From a1cbfe22d52389e2469dbd58838857553474ec7c Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Wed, 17 Jan 2024 20:23:45 -0800 Subject: [PATCH 1/2] Make Scribble Diffusion page static --- components/canvas.js | 85 ---------------- components/github-logo.js | 23 +++++ components/loader.js | 9 -- components/predictions.js | 113 --------------------- components/prompt-form.js | 46 --------- components/replicate-logo.js | 17 ++++ pages/api/og.js | 63 ------------ pages/api/predictions/[id].js | 23 ----- pages/api/predictions/index.js | 71 ------------- pages/api/replicate-webhook.js | 11 -- pages/index.js | 180 ++++++++------------------------- pages/scribbles/[id].js | 39 ------- pages/scribbles/index.js | 36 ------- public/scribble-diffusion.png | Bin 0 -> 2196395 bytes public/scribble-diffusion.webp | Bin 0 -> 112512 bytes 15 files changed, 84 insertions(+), 632 deletions(-) delete mode 100644 components/canvas.js create mode 100644 components/github-logo.js delete mode 100644 components/loader.js delete mode 100644 components/predictions.js delete mode 100644 components/prompt-form.js create mode 100644 components/replicate-logo.js delete mode 100644 pages/api/og.js delete mode 100644 pages/api/predictions/[id].js delete mode 100644 pages/api/predictions/index.js delete mode 100644 pages/api/replicate-webhook.js delete mode 100644 pages/scribbles/[id].js delete mode 100644 pages/scribbles/index.js create mode 100644 public/scribble-diffusion.png create mode 100644 public/scribble-diffusion.webp diff --git a/components/canvas.js b/components/canvas.js deleted file mode 100644 index 30c9162..0000000 --- a/components/canvas.js +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from "react"; -import { useEffect } from "react"; -import { ReactSketchCanvas } from "react-sketch-canvas"; - -import { Undo as UndoIcon, Trash as TrashIcon } from "lucide-react"; - -export default function Canvas({ - startingPaths, - onScribble, - scribbleExists, - setScribbleExists, -}) { - const canvasRef = React.useRef(null); - - useEffect(() => { - // Hack to work around Firfox bug in react-sketch-canvas - // https://github.com/vinothpandian/react-sketch-canvas/issues/54 - document - .querySelector("#react-sketch-canvas__stroke-group-0") - ?.removeAttribute("mask"); - - loadStartingPaths(); - }, []); - - async function loadStartingPaths() { - await canvasRef.current.loadPaths(startingPaths); - setScribbleExists(true); - onChange(); - } - - const onChange = async () => { - const paths = await canvasRef.current.exportPaths(); - localStorage.setItem("paths", JSON.stringify(paths, null, 2)); - - if (!paths.length) return; - - setScribbleExists(true); - - const data = await canvasRef.current.exportImage("png"); - onScribble(data); - }; - - const undo = () => { - canvasRef.current.undo(); - }; - - const reset = () => { - setScribbleExists(false); - canvasRef.current.resetCanvas(); - }; - - return ( -
- {scribbleExists || ( -
-
- Draw something here. -
-
- )} - - - - {scribbleExists && ( -
- - -
- )} -
- ); -} diff --git a/components/github-logo.js b/components/github-logo.js new file mode 100644 index 0000000..4d50f4b --- /dev/null +++ b/components/github-logo.js @@ -0,0 +1,23 @@ +export default function GithubLogo({ + width = "24px", + height = "24px", + fill, + className, + }) { + return ( + + + + ); + } \ No newline at end of file diff --git a/components/loader.js b/components/loader.js deleted file mode 100644 index 1cc0e6b..0000000 --- a/components/loader.js +++ /dev/null @@ -1,9 +0,0 @@ -import PulseLoader from "react-spinners/PulseLoader"; - -export default function Loader() { - return ( -
- -
- ); -} diff --git a/components/predictions.js b/components/predictions.js deleted file mode 100644 index f2f940c..0000000 --- a/components/predictions.js +++ /dev/null @@ -1,113 +0,0 @@ -import copy from "copy-to-clipboard"; -import { Copy as CopyIcon, PlusCircle as PlusCircleIcon } from "lucide-react"; -import Image from "next/image"; -import Link from "next/link"; -import { Fragment, useEffect, useRef, useState } from "react"; -import Loader from "components/loader"; - -export default function Predictions({ predictions, submissionCount }) { - const scrollRef = useRef(null); - - useEffect(() => { - if (submissionCount > 0) { - scrollRef.current.scrollIntoView({ behavior: "smooth" }); - } - }, [predictions, submissionCount]); - - if (submissionCount === 0) return; - - return ( -
-

Results

- - {submissionCount > Object.keys(predictions).length && ( -
-
- -
- )} - - {Object.values(predictions) - .slice() - .reverse() - .map((prediction, index) => ( - - {index === 0 && - submissionCount == Object.keys(predictions).length && ( -
- )} - - - ))} -
- ); -} - -export function Prediction({ prediction, showLinkToNewScribble = false }) { - const [linkCopied, setLinkCopied] = useState(false); - - const copyLink = () => { - const url = - window.location.origin + - "/scribbles/" + - (prediction.uuid || prediction.id); // if the prediction is from the Replicate API it'll have `id`. If it's from the SQL database, it'll have `uuid` - copy(url); - setLinkCopied(true); - }; - - // Clear the "Copied!" message after 4 seconds - useEffect(() => { - const intervalId = setInterval(() => { - setLinkCopied(false); - }, 4 * 1000); - - return () => clearInterval(intervalId); - }, []); - - if (!prediction) return null; - - return ( -
-
-
- input scribble -
-
- {prediction.output?.length ? ( - output image - ) : ( -
- -
- )} -
-
-
- “{prediction.input.prompt}” -
-
- - - {showLinkToNewScribble && ( - - - - )} -
-
- ); -} diff --git a/components/prompt-form.js b/components/prompt-form.js deleted file mode 100644 index 1ed4e38..0000000 --- a/components/prompt-form.js +++ /dev/null @@ -1,46 +0,0 @@ -import { useEffect, useState } from "react"; - -export default function PromptForm({ - initialPrompt, - onSubmit, - scribbleExists, -}) { - const [prompt, setPrompt] = useState(initialPrompt); - - const disabled = !(scribbleExists && prompt?.length > 0); - - useEffect(() => { - setPrompt(initialPrompt); - }, [initialPrompt]); - - const handleSubmit = (e) => { - e.preventDefault(); - onSubmit(e); - }; - - return ( -
-
- setPrompt(e.target.value)} - placeholder="Describe the image you want to create..." - className="block w-full flex-grow rounded-l-md" - /> - - -
-
- ); -} diff --git a/components/replicate-logo.js b/components/replicate-logo.js new file mode 100644 index 0000000..6e0c663 --- /dev/null +++ b/components/replicate-logo.js @@ -0,0 +1,17 @@ +export default function ReplicateLogo({ width, height, className, fill }) { + return ( + + + + + + ); + } \ No newline at end of file diff --git a/pages/api/og.js b/pages/api/og.js deleted file mode 100644 index 4c3be93..0000000 --- a/pages/api/og.js +++ /dev/null @@ -1,63 +0,0 @@ -import { ImageResponse } from "@vercel/og"; -import { NextRequest } from "next/server"; -import Image from "next/image"; - -export const config = { - runtime: "edge", -}; - -// This endpoint take a query parameter `id` which is the ID of a prediction -// and returns an Open Graph image for that prediction -export default async function handler(req) { - const { searchParams } = req.nextUrl; - const predictionId = searchParams.get("id"); - let inputImageURL, outputImageURL; - - // extract protocol and host from the request url, so we can call the local API - const url = new URL(req.url); - const protocol = url.protocol; - const host = url.host; - - if (predictionId) { - const response = await fetch( - `${protocol}//${host}/api/predictions/${predictionId}` - ); - const prediction = await response.json(); - - if (response.status === 200) { - inputImageURL = prediction.input.image; - outputImageURL = prediction.output?.[prediction.output.length - 1]; - } - } - - // Fallback to a default image - if (!inputImageURL) { - inputImageURL = - "https://upcdn.io/FW25b4F/raw/uploads/scribble-diffusion/1.0.0/2023-02-17/scribble_input_2JrhzKQH.png"; - outputImageURL = - "https://replicate.delivery/pbxt/fX5WyhTJ2LQ7ZC4Pq1TLZaHfLdEciFyfmKxU7FN8WLZhhaeBB/output_1.png"; - } - - return new ImageResponse( - ( -
- img - img -
- ), - { - width: 1200, - height: 630, - } - ); -} diff --git a/pages/api/predictions/[id].js b/pages/api/predictions/[id].js deleted file mode 100644 index 9e3163c..0000000 --- a/pages/api/predictions/[id].js +++ /dev/null @@ -1,23 +0,0 @@ -import { NextResponse } from "next/server"; -import Replicate from "replicate"; -import packageData from "../../../package.json"; - -const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN, - userAgent: `${packageData.name}/${packageData.version}`, -}); - -export default async function handler(req) { - const predictionId = req.nextUrl.searchParams.get("id"); - const prediction = await replicate.predictions.get(predictionId); - - if (prediction?.error) { - return NextResponse.json({ detail: prediction.error }, { status: 500 }); - } - - return NextResponse.json(prediction); -} - -export const config = { - runtime: "edge", -}; diff --git a/pages/api/predictions/index.js b/pages/api/predictions/index.js deleted file mode 100644 index 010105d..0000000 --- a/pages/api/predictions/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import { NextResponse } from "next/server"; -import Replicate from "replicate"; -import packageData from "../../../package.json"; - -const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN, - userAgent: `${packageData.name}/${packageData.version}`, -}); - -async function getObjectFromRequestBodyStream(body) { - const input = await body.getReader().read(); - const decoder = new TextDecoder(); - const string = decoder.decode(input.value); - return JSON.parse(string); -} - -const WEBHOOK_HOST = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : process.env.NGROK_HOST; - -export default async function handler(req) { - 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 input = await getObjectFromRequestBodyStream(req.body); - - const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN, - }); - - let prediction; - - if (process.env.USE_REPLICATE_DEPLOYMENT) { - prediction = await replicate.deployments.predictions.create( - "replicate", - "scribble-diffusion-jagilley-controlnet", - { - input, - webhook: `${WEBHOOK_HOST}/api/replicate-webhook`, - webhook_events_filter: ["start", "completed"], - } - ); - } else { - // https://replicate.com/jagilley/controlnet-scribble/versions - prediction = await replicate.predictions.create({ - version: - "435061a1b5a4c1e26740464bf786efdfa9cb3a3ac488595a2de23e143fdb0117", - input, - webhook: `${WEBHOOK_HOST}/api/replicate-webhook`, - webhook_events_filter: ["start", "completed"], - }); - } - - if (prediction?.error) { - return NextResponse.json({ detail: prediction.error }, { status: 500 }); - } - - return NextResponse.json(prediction, { status: 201 }); -} - -export const config = { - runtime: "edge", - api: { - bodyParser: { - sizeLimit: "10mb", - }, - }, -}; diff --git a/pages/api/replicate-webhook.js b/pages/api/replicate-webhook.js deleted file mode 100644 index bee901f..0000000 --- a/pages/api/replicate-webhook.js +++ /dev/null @@ -1,11 +0,0 @@ -// The Replicate webhook is a POST request where the request body is a prediction object. -// Identical webhooks can be sent multiple times, so this handler must be idempotent. - -import { upsertPrediction } from "../../lib/db"; - -export default async function handler(req, res) { - console.log("received webhook for prediction: ", req.body.id); - await upsertPrediction(req.body); - - res.end(); -} diff --git a/pages/index.js b/pages/index.js index b12d439..517a01a 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1,142 +1,50 @@ -import Canvas from "components/canvas"; -import PromptForm from "components/prompt-form"; -import Head from "next/head"; -import Link from "next/link"; -import { useState } from "react"; -import Predictions from "components/predictions"; -import Error from "components/error"; -import uploadFile from "lib/upload"; -import naughtyWords from "naughty-words"; -import Script from "next/script"; -import seeds from "lib/seeds"; -import pkg from "../package.json"; -import sleep from "lib/sleep"; - -const HOST = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : "http://localhost:3000"; +import GithubLogo from "../components/github-logo"; +import ReplicateLogo from "../components/replicate-logo"; export default function Home() { - const [error, setError] = useState(null); - const [submissionCount, setSubmissionCount] = useState(0); - const [predictions, setPredictions] = useState({}); - const [isProcessing, setIsProcessing] = useState(false); - const [scribbleExists, setScribbleExists] = useState(false); - const [seed] = useState(seeds[Math.floor(Math.random() * seeds.length)]); - const [initialPrompt] = useState(seed.prompt); - const [scribble, setScribble] = useState(null); - - const handleSubmit = async (e) => { - e.preventDefault(); - - // track submissions so we can show a spinner while waiting for the next prediction to be created - setSubmissionCount(submissionCount + 1); - - const prompt = e.target.prompt.value - .split(/\s+/) - .map((word) => (naughtyWords.en.includes(word) ? "something" : word)) - .join(" "); - - setError(null); - setIsProcessing(true); - - const fileUrl = await uploadFile(scribble); - - const body = { - prompt, - image: fileUrl, - structure: "scribble", - }; - - const response = await fetch("/api/predictions", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - let prediction = await response.json(); - - setPredictions((predictions) => ({ - ...predictions, - [prediction.id]: prediction, - })); - - if (response.status !== 201) { - setError(prediction.detail); - return; - } - - while ( - prediction.status !== "succeeded" && - prediction.status !== "failed" - ) { - await sleep(500); - const response = await fetch("/api/predictions/" + prediction.id); - prediction = await response.json(); - setPredictions((predictions) => ({ - ...predictions, - [prediction.id]: prediction, - })); - if (response.status !== 200) { - setError(prediction.detail); - return; - } - } - - setIsProcessing(false); - }; - return ( - <> - - {pkg.appName} - - - - - - - -
-
-
-

- {pkg.appName} -

-

- {pkg.appSubtitle} -

-
- - - - - - +
+
+
+

+ 🖍️ From rough sketches to masterpieces with Scribble Diffusion using {" "} + + ControlNet + + . +

+ +
- -
- -