Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

พัฒนาแอปฟื้นฟูรูปภาพโดยใช้ Replicate, Next.js และ Upstash

สำหรับโพสต์บนบล็อกนี้ เราจะตั้งสมมติฐานบางประการก่อนดำเนินการต่อ แต่คุณควรมี:

  • บัญชี Upstash ที่คุณได้สร้างอินสแตนซ์ Redis
  • บัญชีจำลองที่สามารถเข้าถึงโทเค็น API ของคุณ
  • โครงการ Next.js เพื่อใช้ฟังก์ชันการทำงานที่เราต้องการ
  • บัญชี Vercel เพื่อปรับใช้โครงการของคุณ

นี่คืออะไร?

คุณเคยต้องการเริ่มต้นใช้งาน Machine Learning เพื่อสร้างภาพจากโมเดลที่มีอยู่ใน Replicate หรือไม่? ในบทช่วยสอนนี้ เราจะสำรวจโมเดลโฮสต์ที่หลากหลายของ Replicate และ Redis ของ Upstash เราจะไม่เพียงแต่สำรวจโมเดลเหล่านี้เท่านั้น แต่เราจะอธิบายขั้นตอนการตั้งค่าและสัมผัสถึงวิธีที่คุณสามารถอัปเดตการใช้งานเพื่อใช้โมเดลอื่นๆ ได้อย่างง่ายดาย

ในบทช่วยสอนนี้ เราจะกล่าวถึงการใช้งานโมเดล Bringing Old Photos Back to Life ของ Microsoft ซึ่งโดยหลักแล้วจะใช้รูปภาพเก่า เรียกใช้ผ่านโมเดล และแสดงผลรูปภาพของคุณในเวอร์ชันที่แก้ไขแล้ว และหวังว่าจะได้รับการปรับปรุง

พัฒนาแอปฟื้นฟูรูปภาพโดยใช้ Replicate, Next.js และ Upstash

สถาปัตยกรรมคืออะไร

หากคุณมีประสบการณ์ React มาก่อน คุณควรจะกำหนดได้ว่าสถาปัตยกรรมของแอปทำงานอย่างไรได้ง่ายๆ โดยการอ่านผ่านโค้ดเบส แต่เพื่อให้ง่ายขึ้นอีกนิด หรือหากคุณเพียงต้องการดูภาพรวม เราจึงมีให้ไว้ด้านล่าง

พัฒนาแอปฟื้นฟูรูปภาพโดยใช้ Replicate, Next.js และ Upstash

ฉันต้องเริ่มต้นอย่างไร?

ในการเริ่มต้น คุณจะต้องมีโปรเจ็กต์ Next.js ซึ่งสามารถทำได้โดยทำตามคำแนะนำการตั้งค่า Next.js ที่นี่ หรือหากคุณได้ตั้งค่าไว้แล้ว ก็ไม่เป็นไรเช่นกัน ในบทช่วยสอนนี้ เรายังใช้ Tailwind CSS อยู่ แต่คุณสามารถใช้การตั้งค่าการจัดรูปแบบรูปแบบใดก็ได้ตามที่คุณต้องการ

ตอนนี้เรามีการตั้งค่าโปรเจ็กต์ Next.js พื้นฐานแล้ว เรายังคงสามารถไลบรารี Redis ของ Upstash ได้โดยการรันคำสั่ง:

npm install @upstash/redis

ต่อไป เราจะต้องการเติม 05 ของเรา ไฟล์ที่มีคีย์ต่อไปนี้ ซึ่งสามารถพบได้ในโทเค็น Redis ในคอนโซล Upstash ของคุณ โทเค็น Replicate API ที่นี่ภายใต้บัญชีของคุณ และ URL ไซต์ของคุณจะอยู่ทุกที่ที่คุณปรับใช้ ดังนั้นในกรณีนี้จะเป็นจุดสิ้นสุดการปรับใช้ Vercel

SITE_URL=https://your-project-url.vercel.app
 
REPLICATE_API_TOKEN=
 
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

การตั้งค่าแบบฟอร์มส่วนหน้า

ขั้นแรก เราจะต้องมีแบบฟอร์มที่จัดการแบบฟอร์ม การโพล และการแสดงภาพที่เสร็จสมบูรณ์

คืนค่าการสร้างแบบฟอร์มรูปภาพ

ไฟล์:12

import { MouseEvent, RefObject, useRef, useState } from "react";
import Head from "next/head";
 
import useInterval from "../hooks/useInterval";
 
export default function Home() {
 const [restoring, setRestoring] = useState<boolean>(false);
 const [messageId, setMessageId] = useState<string | null>(null);
 const [prediction, setPrediction] = useState<any>({});
 const [outputImageUrl, setOutputImageUrl] = useState<string | null>(null);
 const imageUrlRef: RefObject<HTMLInputElement> = useRef(null);
 const hrRef: RefObject<HTMLInputElement> = useRef(null);
 const scratchRef: RefObject<HTMLInputElement> = useRef(null);
 
 useInterval(
 async () => {
 await fetch(`/api/poll?id=${messageId}`)
 .then((res: any) => res.json())
 .then((data: any) => {
 if (!data.output) {
 return;
 }
 
 setRestoring(false);
 setMessageId(null);
 setOutputImageUrl(data.output);
 })
 .catch((err: any) => console.error(err));
 },
 messageId ? 1000 : null,
 );
 
 async function restoreImage(e: any) {
 e.preventDefault();
 
 setRestoring(true);
 
 await fetch("/api/create", {
 method: "POST",
 body: JSON.stringify({
 image_url: imageUrlRef.current?.value,
 is_hr: hrRef.current?.value,
 has_scratches: scratchRef.current?.value,
 }),
 headers: { "Content-Type": "application/json" },
 })
 .then((res: Response) => res.json())
 .then((data: any) => {
 setMessageId(data.data.id);
 setPrediction(data.data);
 })
 .catch((err: Error) => console.error(err));
 }
 
 async function cancel(e: MouseEvent<HTMLButtonElement>) {
 e.preventDefault();
 
 await fetch("/api/cancel", {
 method: "POST",
 headers: { "Content-Type": "application/json" },
 body: JSON.stringify({ cancel_url: prediction.urls.cancel }),
 })
 .then((res: Response) => res.json())
 .then((data: any) => {
 setMessageId(null);
 setPrediction({});
 setRestoring(false);
 })
 .catch((err: Error) => console.error(err));
 }
 
 return (
 <>
 <Head>
 <title>PhotoRescue</title>
 <meta
 name="description"
 content="A simple Next.js application that utilizes Replicate to restore old photos."
 />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <link rel="icon" href="/favicon.ico" />
 </Head>
 <main>
 <div className="my-16 flex flex-col items-center justify-center md:my-32">
 <h1 className="text-5xl font-black">PhotoRescue</h1>
 
 <p className="mt-4">Restore your old photos to their former glory.</p>
 
 {outputImageUrl && (
 <div className="flex flex-col items-center justify-center">
 <img
 src={outputImageUrl}
 alt="Restored Image"
 className="mt-8 h-auto w-72"
 />
 
 <button
 type="button"
 onClick={() => setOutputImageUrl(null)}
 className="mt-8 inline-flex items-center rounded-full border border-transparent bg-gray-900 px-6 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2 disabled:opacity-50"
 >
 Start Again
 </button>
 </div>
 )}
 
 {!outputImageUrl && (
 <form
 onSubmit={restoreImage}
 className="mt-10 flex w-full max-w-lg flex-col items-center"
 >
 <div className="w-full space-y-4">
 <div>
 <label htmlFor="image_url" className="text-sm font-semibold">
 Image URL
 </label>
 <input
 name="image_url"
 id="image_url"
 type="text"
 defaultValue="https://replicate.delivery/mgxm/b033ff07-1d2e-4768-a137-6c16b5ed4bed/d_1.png"
 placeholder="https://example.com/image.png"
 className="mt-0.5 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-gray-500 focus:ring-gray-500"
 ref={imageUrlRef}
 required
 />
 </div>
 <div className="max-w-lg space-y-4">
 <div className="relative flex items-start">
 <div className="flex h-5 items-center">
 <input
 name="is_hr"
 id="is_hr"
 type="checkbox"
 className="h-4 w-4 rounded border-gray-300 text-gray-900 focus:ring-gray-500"
 ref={hrRef}
 />
 </div>
 <div className="ml-3 text-sm">
 <label
 htmlFor="is_hr"
 className="font-medium text-gray-900"
 >
 Is High Resolution?
 </label>
 <p className="text-gray-500">
 Check this if the input image is a high resolution
 photo.
 </p>
 </div>
 </div>
 <div className="relative flex items-start">
 <div className="flex h-5 items-center">
 <input
 name="is_scratched"
 id="is_scratched"
 type="checkbox"
 className="h-4 w-4 rounded border-gray-300 text-gray-900 focus:ring-gray-500"
 ref={scratchRef}
 defaultChecked={true}
 />
 </div>
 <div className="ml-3 text-sm">
 <label
 htmlFor="is_scratched"
 className="font-medium text-gray-900"
 >
 Has Scratches?
 </label>
 <p className="text-gray-500">
 Check this if the input image has visible scratches over
 it.
 </p>
 </div>
 </div>
 </div>
 </div>
 
 <div className="mt-6 flex gap-2">
 <button
 type="submit"
 disabled={restoring}
 className="inline-flex items-center rounded-full border border-transparent bg-gray-900 px-6 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2 disabled:opacity-50"
 >
 {restoring ? "Restoring..." : "Restore"}
 </button>
 
 {restoring && prediction && (
 <button
 type="button"
 onClick={cancel}
 className="inline-flex items-center rounded-full border border-gray-900 bg-white px-6 py-2.5 text-sm font-medium text-gray-900 shadow-sm hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2"
 >
 Cancel
 </button>
 )}
 </div>
 </form>
 )}
 </div>
 </main>
 </>
 );
}

ตามค่าเริ่มต้น ส่วนประกอบนี้จะแสดงแบบฟอร์มที่อนุญาตให้ผู้ใช้ป้อน URL รูปภาพของรูปภาพที่ต้องการกู้คืน และตัวเลือกสองสามตัวที่ใช้ร่วมกับแบบฟอร์มนั้น เช่น รูปภาพมีความละเอียดสูงหรือไม่ หรือรูปภาพมีรอยขีดข่วนที่ต้องลบออกหรือไม่ เมื่อผู้ใช้กรอกข้อมูลนี้ และส่งแบบฟอร์มแล้ว ระบบจะส่ง POST ขอ 27 พร้อมด้วยข้อมูลแบบฟอร์ม

เมื่อคำขอนี้ถูกส่งไปยัง API และได้รับการตอบกลับด้วยข้อมูลการคาดการณ์ที่ถูกส่งกลับ ส่วนประกอบจะเข้าสู่สถานะการโพลที่เช็คส่ง GET ขอ 32 หนึ่งครั้งต่อวินาทีเพื่อตรวจสอบว่าการทำนายเสร็จสมบูรณ์หรือยัง เมื่อคำขอโพลส่งคืนการตอบกลับที่สำเร็จ โดยระบุว่า Replicate ได้ส่งคำขอไปยังปลายทางการโทรกลับของเราแล้ว ตอนนี้เราจะสามารถเข้าถึงเอาต์พุตการคาดการณ์ได้

ขณะที่การสำรวจกำลังดำเนินอยู่ แบบฟอร์มจะแสดงปุ่มพร้อมตัวเลือกในการยกเลิกการทำนาย เมื่อกดแล้ว ระบบจะส่ง POST ขอ 44 ด้วย 57 จากข้อมูลการคาดการณ์ที่เราได้รับเมื่อสร้างครั้งแรก

การใช้งานการสำรวจใช้ hook แบบกำหนดเองที่จะอยู่ใน 63 ซึ่งช่วยให้เราสามารถทำงานกับไลฟ์สไตล์ส่วนประกอบของ React ได้อย่างง่ายดายและราบรื่น และมอบวิธีที่สะดวกยิ่งขึ้นในการจัดการช่วงเวลาที่มีการโทรกลับภายในส่วนประกอบ React ใดๆ ที่กำหนด คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเบ็ดนี้ได้ที่นี่และที่นี่ หากคุณต้องการเรียนรู้เพิ่มเติมและรายละเอียดเพิ่มเติม

import { useEffect, useRef } from "react";
 
function useInterval(callback: () => void, delay: number | null) {
 const savedCallback = useRef(callback);
 
 useEffect(() => {
 savedCallback.current = callback;
 }, [callback]);
 
 useEffect(() => {
 if (!delay && delay !== 0) {
 return;
 }
 
 const id = setInterval(() => savedCallback.current(), delay);
 
 return () => clearInterval(id);
 }, [delay]);
}
 
export default useInterval;

การตั้งค่า API

การตั้งค่า API ซึ่งประกอบด้วยไฟล์ไม่กี่ไฟล์ เป็นสิ่งที่ช่วยให้เราสามารถสร้างและยกเลิกการคาดคะเน สำรวจเพื่อตรวจสอบเมื่อการคาดการณ์เสร็จสมบูรณ์ รวมถึงระบุการโทรกลับที่ Replicate จะใช้เมื่อการคาดการณ์เสร็จสมบูรณ์ในตอนท้าย

การสร้างการคาดการณ์ภาพ

ไฟล์:77

import type { NextApiRequest, NextApiResponse } from "next";
 
import fetch, { Response } from "node-fetch";
 
import redis from "../../lib/redis";
 
export default async function handler(
 req: NextApiRequest,
 res: NextApiResponse,
) {
 if (req.method !== "POST") {
 return res.status(400).json({
 message: `Invalid request method: ${req.method}.`,
 });
 }
 
 const { image_url, is_hr, has_scratches }: any = req.body;
 
 await fetch("https://api.replicate.com/v1/predictions", {
 method: "POST",
 headers: {
 Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`,
 "Content-Type": "application/json",
 },
 body: JSON.stringify({
 version:
 "c75db81db6cbd809d93cc3b7e7a088a351a3349c9fa02b6d393e35e0d51ba799",
 input: {
 image: image_url,
 HR: is_hr,
 with_scratch: has_scratches,
 },
 webhook_completed: `${process.env.SITE_URL}/api/callback`,
 }),
 })
 .then((res: Response) => res.json())
 .then(async (data: any) => {
 await redis.set(data.id, data);
 
 return res.status(202).json({ data: data });
 })
 .catch((error: Error) => {
 return res.status(500).json({ message: error.message });
 });
}

สำหรับการสร้างตำแหน่งข้อมูล API ของเรา ขั้นแรกเราจะทำการตรวจสอบง่ายๆ เพื่อให้แน่ใจว่าวิธีการร้องขอที่เข้ามานั้นเป็น POST ร้องขอ และหากไม่ เราจะตอบกลับ 400 คำตอบธรรมดาๆ จากนั้นเราจะดำเนินการส่ง POST ขอจำลองด้วยโทเค็น Replicate API ของเรา เนื้อหาของคำขอประกอบด้วยพารามิเตอร์สำหรับโมเดลที่กำหนด 84 ซึ่งระบุว่าเรากำลังส่งคำขอไปยังโมเดลใด (อยู่ใต้แท็บ "API" บนโมเดลที่คุณต้องการใช้) นอกจากนี้เรายังส่งผ่านพารามิเตอร์ที่เกี่ยวข้องกับโมเดลด้วยข้อมูลจากแบบฟอร์มที่ส่วนหน้า

เมื่อส่งคำขอแล้ว เราจะใช้การคาดคะเนที่ส่งคืน 95 เพื่อจัดเก็บไว้ใน Redis และส่งคืนข้อมูลการคาดการณ์ไปยังส่วนหน้าเพื่อใช้ในการสำรวจรายการ Redis จนกว่าจะประกอบด้วยการคาดการณ์ที่เสร็จสมบูรณ์

โทรกลับ

ไฟล์:100

import type { NextApiRequest, NextApiResponse } from "next";
 
import redis from "../../lib/redis";
 
export default async function handler(
 req: NextApiRequest,
 res: NextApiResponse,
) {
 const { body }: any = req;
 
 try {
 await redis.set(body.id, body);
 
 return res.status(200).send(body);
 } catch (error) {
 return res.status(500).json({ error });
 }
}

จุดสิ้นสุดการติดต่อกลับคือสิ่งที่ Replicate จะส่ง POST ร้องขอเพื่อแจ้งให้เราทราบว่าการประมวลผลคำทำนายที่กำหนดได้เสร็จสิ้นแล้ว เมื่อเราได้รับคำขอนี้ เราจะดึงข้อมูลการคาดการณ์จากเนื้อหาคำขอ และอัปเดตรายการ Redis ที่กำหนดด้วยข้อมูลการคาดการณ์ที่ครบถ้วน

การสำรวจความคิดเห็น

ไฟล์:117

import type { NextApiRequest, NextApiResponse } from "next";
 
import redis from "../../lib/redis";
 
export default async function handler(
 req: NextApiRequest,
 res: NextApiResponse,
) {
 const { id }: any = req.query;
 
 try {
 const data = await redis.get(id);
 
 if (!data) {
 return res
 .status(404)
 .json({ message: "Data for supplied ID not found" });
 }
 
 return res.status(200).json(data);
 } catch (error: any) {
 return res.status(500).json({ message: error.message });
 }
}

สำหรับการตั้งค่าการสำรวจความคิดเห็นของเรา เราจะแยก 127 จากคำขอแล้วลองดึงข้อมูลที่จัดเก็บไว้ใน Redis ภายใต้ตัวระบุนั้น และหากไม่พบข้อมูล เราจะส่งคืนการตอบกลับ 404 แต่หากมีข้อมูล เราจะส่งคืนข้อมูลดังกล่าวโดยเป็นส่วนหนึ่งของการตอบกลับ 200 ครั้ง

ยกเลิก

ไฟล์:136

import type { NextApiRequest, NextApiResponse } from "next";
 
import fetch, { Response } from "node-fetch";
 
export default async function handler(
 req: NextApiRequest,
 res: NextApiResponse,
) {
 if (req.method !== "POST") {
 return res.status(400).json({
 message: `Invalid request method: ${req.method}.`,
 });
 }
 
 const { cancel_url }: any = req.body;
 
 await fetch(cancel_url, {
 method: "POST",
 headers: {
 Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`,
 "Content-Type": "application/json",
 },
 })
 .then((res: Response) => res.json())
 .then((data: any) => {
 return res.status(202).json({ data: data });
 })
 .catch((error: Error) => {
 return res.status(500).json({ message: error.message });
 });
}

ตำแหน่งข้อมูล API สำหรับการยกเลิกการคาดการณ์ที่เริ่มต้นแล้วนั้นค่อนข้างตรงไปตรงมา เราเพียงแยก 149 ซึ่งส่งผ่านจากส่วนหน้า ซึ่งในตัวมันเองมาจากการคาดการณ์ที่เก็บไว้เมื่อมีการส่งคำขอสร้าง และเราก็ส่ง POST ร้องขอไปยังตำแหน่งข้อมูลนั้น ควบคู่ไปกับโทเค็น Replicate API ของเรา

ห้องสมุด

สำหรับ libs ของเรา เราจะสร้างไคลเอ็นต์ Redis ซึ่งใช้ในการติดตาม

ไฟล์:159

import { Redis } from "@upstash/redis";
 
const redis = new Redis({
 url: process.env.UPSTASH_REDIS_REST_URL as string,
 token: process.env.UPSTASH_REDIS_REST_TOKEN as string,
});
 
export default redis;

ออบเจ็กต์นี้จะถูกใช้ภายในแอปพลิเคชันเพื่อจัดเก็บและดึงข้อมูลในขณะที่กำลังสำรวจ เพื่อให้เราทราบว่าเมื่อใดที่เว็บฮุคเสร็จสมบูรณ์จาก Replicate เกิดขึ้น

บทสรุป

การทำซ้ำมีโมเดลหลากหลายให้เลือกใช้ผ่าน API ด้วย Vercel และ Upstash การใช้โมเดลแมชชีนเลิร์นนิงและปรับใช้แอปพลิเคชันเว็บที่ใช้งานได้ง่ายกว่าที่เคย

หากคุณต้องการดูพื้นที่เก็บข้อมูลทั้งหมด คุณสามารถเข้าถึงได้ที่นี่

การพัฒนาเพิ่มเติม

นี่เป็นเพียงตัวอย่างง่ายๆ ของการใช้โมเดลที่ค่อนข้างง่ายกับ Replicate เพียงสลับพารามิเตอร์แบบฟอร์มและเวอร์ชันใน API คุณก็สามารถเปลี่ยนไปใช้โมเดลอื่นได้อย่างง่ายดาย ตราบใดที่คุณมีโทเค็น Replicate API ลิงก์อยู่ คุณจะสามารถใช้โมเดลที่มีอยู่ได้

คุณสามารถสำรวจโมเดลที่มีอยู่ทั้งหมดของ Replicate ได้ที่นี่ และเมื่อคุณพบโมเดลที่คุณต้องการทดลองใช้แล้ว คุณสามารถคลิกแท็บ "API" เพื่อดูการใช้งานได้ ที่นี่ คุณยังจะพบปุ่มสำหรับ Python, cURL, Cog และ Docker ซึ่งช่วยให้คุณสามารถทดสอบโมเดลได้ แต่ยังมีประโยชน์ในการทราบว่าพารามิเตอร์ใดที่จำเป็น และวิธีการส่งพารามิเตอร์เหล่านั้นอีกด้วย