สำหรับโพสต์บนบล็อกนี้ เราจะตั้งสมมติฐานบางประการก่อนดำเนินการต่อ แต่คุณควรมี:
- บัญชี Upstash ที่คุณได้สร้างอินสแตนซ์ Redis
- บัญชีจำลองที่สามารถเข้าถึงโทเค็น API ของคุณ
- โครงการ Next.js เพื่อใช้ฟังก์ชันการทำงานที่เราต้องการ
- บัญชี Vercel เพื่อปรับใช้โครงการของคุณ
นี่คืออะไร?
คุณเคยต้องการเริ่มต้นใช้งาน Machine Learning เพื่อสร้างภาพจากโมเดลที่มีอยู่ใน Replicate หรือไม่? ในบทช่วยสอนนี้ เราจะสำรวจโมเดลโฮสต์ที่หลากหลายของ Replicate และ Redis ของ Upstash เราจะไม่เพียงแต่สำรวจโมเดลเหล่านี้เท่านั้น แต่เราจะอธิบายขั้นตอนการตั้งค่าและสัมผัสถึงวิธีที่คุณสามารถอัปเดตการใช้งานเพื่อใช้โมเดลอื่นๆ ได้อย่างง่ายดาย
ในบทช่วยสอนนี้ เราจะกล่าวถึงการใช้งานโมเดล Bringing Old Photos Back to Life ของ Microsoft ซึ่งโดยหลักแล้วจะใช้รูปภาพเก่า เรียกใช้ผ่านโมเดล และแสดงผลรูปภาพของคุณในเวอร์ชันที่แก้ไขแล้ว และหวังว่าจะได้รับการปรับปรุง

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

ฉันต้องเริ่มต้นอย่างไร?
ในการเริ่มต้น คุณจะต้องมีโปรเจ็กต์ 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= การตั้งค่าแบบฟอร์มส่วนหน้า
ขั้นแรก เราจะต้องมีแบบฟอร์มที่จัดการแบบฟอร์ม การโพล และการแสดงภาพที่เสร็จสมบูรณ์
คืนค่าการสร้างแบบฟอร์มรูปภาพ
ไฟล์:
ตามค่าเริ่มต้น ส่วนประกอบนี้จะแสดงแบบฟอร์มที่อนุญาตให้ผู้ใช้ป้อน URL รูปภาพของรูปภาพที่ต้องการกู้คืน และตัวเลือกสองสามตัวที่ใช้ร่วมกับแบบฟอร์มนั้น เช่น รูปภาพมีความละเอียดสูงหรือไม่ หรือรูปภาพมีรอยขีดข่วนที่ต้องลบออกหรือไม่ เมื่อผู้ใช้กรอกข้อมูลนี้ และส่งแบบฟอร์มแล้ว ระบบจะส่ง POST ขอ
เมื่อคำขอนี้ถูกส่งไปยัง API และได้รับการตอบกลับด้วยข้อมูลการคาดการณ์ที่ถูกส่งกลับ ส่วนประกอบจะเข้าสู่สถานะการโพลที่เช็คส่ง GET ขอ
ขณะที่การสำรวจกำลังดำเนินอยู่ แบบฟอร์มจะแสดงปุ่มพร้อมตัวเลือกในการยกเลิกการทำนาย เมื่อกดแล้ว ระบบจะส่ง POST ขอ
การใช้งานการสำรวจใช้ hook แบบกำหนดเองที่จะอยู่ใน
การตั้งค่า API ซึ่งประกอบด้วยไฟล์ไม่กี่ไฟล์ เป็นสิ่งที่ช่วยให้เราสามารถสร้างและยกเลิกการคาดคะเน สำรวจเพื่อตรวจสอบเมื่อการคาดการณ์เสร็จสมบูรณ์ รวมถึงระบุการโทรกลับที่ Replicate จะใช้เมื่อการคาดการณ์เสร็จสมบูรณ์ในตอนท้าย
ไฟล์:
สำหรับการสร้างตำแหน่งข้อมูล API ของเรา ขั้นแรกเราจะทำการตรวจสอบง่ายๆ เพื่อให้แน่ใจว่าวิธีการร้องขอที่เข้ามานั้นเป็น POST ร้องขอ และหากไม่ เราจะตอบกลับ 400 คำตอบธรรมดาๆ จากนั้นเราจะดำเนินการส่ง POST ขอจำลองด้วยโทเค็น Replicate API ของเรา เนื้อหาของคำขอประกอบด้วยพารามิเตอร์สำหรับโมเดลที่กำหนด
เมื่อส่งคำขอแล้ว เราจะใช้การคาดคะเนที่ส่งคืน
ไฟล์:
จุดสิ้นสุดการติดต่อกลับคือสิ่งที่ Replicate จะส่ง POST ร้องขอเพื่อแจ้งให้เราทราบว่าการประมวลผลคำทำนายที่กำหนดได้เสร็จสิ้นแล้ว เมื่อเราได้รับคำขอนี้ เราจะดึงข้อมูลการคาดการณ์จากเนื้อหาคำขอ และอัปเดตรายการ Redis ที่กำหนดด้วยข้อมูลการคาดการณ์ที่ครบถ้วน
ไฟล์:
สำหรับการตั้งค่าการสำรวจความคิดเห็นของเรา เราจะแยก
ไฟล์:
ตำแหน่งข้อมูล API สำหรับการยกเลิกการคาดการณ์ที่เริ่มต้นแล้วนั้นค่อนข้างตรงไปตรงมา เราเพียงแยก
สำหรับ libs ของเรา เราจะสร้างไคลเอ็นต์ Redis ซึ่งใช้ในการติดตาม
ไฟล์:
ออบเจ็กต์นี้จะถูกใช้ภายในแอปพลิเคชันเพื่อจัดเก็บและดึงข้อมูลในขณะที่กำลังสำรวจ เพื่อให้เราทราบว่าเมื่อใดที่เว็บฮุคเสร็จสมบูรณ์จาก Replicate เกิดขึ้น
การทำซ้ำมีโมเดลหลากหลายให้เลือกใช้ผ่าน API ด้วย Vercel และ Upstash การใช้โมเดลแมชชีนเลิร์นนิงและปรับใช้แอปพลิเคชันเว็บที่ใช้งานได้ง่ายกว่าที่เคย
หากคุณต้องการดูพื้นที่เก็บข้อมูลทั้งหมด คุณสามารถเข้าถึงได้ที่นี่
นี่เป็นเพียงตัวอย่างง่ายๆ ของการใช้โมเดลที่ค่อนข้างง่ายกับ Replicate เพียงสลับพารามิเตอร์แบบฟอร์มและเวอร์ชันใน API คุณก็สามารถเปลี่ยนไปใช้โมเดลอื่นได้อย่างง่ายดาย ตราบใดที่คุณมีโทเค็น Replicate API ลิงก์อยู่ คุณจะสามารถใช้โมเดลที่มีอยู่ได้
คุณสามารถสำรวจโมเดลที่มีอยู่ทั้งหมดของ Replicate ได้ที่นี่ และเมื่อคุณพบโมเดลที่คุณต้องการทดลองใช้แล้ว คุณสามารถคลิกแท็บ "API" เพื่อดูการใช้งานได้ ที่นี่ คุณยังจะพบปุ่มสำหรับ Python, cURL, Cog และ Docker ซึ่งช่วยให้คุณสามารถทดสอบโมเดลได้ แต่ยังมีประโยชน์ในการทราบว่าพารามิเตอร์ใดที่จำเป็น และวิธีการส่งพารามิเตอร์เหล่านั้นอีกด้วย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>
</>
);
}27 พร้อมด้วยข้อมูลแบบฟอร์ม32 หนึ่งครั้งต่อวินาทีเพื่อตรวจสอบว่าการทำนายเสร็จสมบูรณ์หรือยัง เมื่อคำขอโพลส่งคืนการตอบกลับที่สำเร็จ โดยระบุว่า Replicate ได้ส่งคำขอไปยังปลายทางการโทรกลับของเราแล้ว ตอนนี้เราจะสามารถเข้าถึงเอาต์พุตการคาดการณ์ได้44 ด้วย 57 จากข้อมูลการคาดการณ์ที่เราได้รับเมื่อสร้างครั้งแรก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
การสร้างการคาดการณ์ภาพ
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 });
});
}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 });
}
}การสำรวจความคิดเห็น
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 });
});
}149 ซึ่งส่งผ่านจากส่วนหน้า ซึ่งในตัวมันเองมาจากการคาดการณ์ที่เก็บไว้เมื่อมีการส่งคำขอสร้าง และเราก็ส่ง POST ร้องขอไปยังตำแหน่งข้อมูลนั้น ควบคู่ไปกับโทเค็น Replicate API ของเราห้องสมุด
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;บทสรุป
การพัฒนาเพิ่มเติม