Đối với bài đăng blog này, chúng tôi sẽ đưa ra một số giả định trước khi tiếp tục, nhưng lý tưởng nhất là bạn nên có:
- Tài khoản Upstash nơi bạn đã tạo phiên bản Redis
- Tài khoản sao chép có quyền truy cập vào mã thông báo API của bạn
- Dự án Next.js để triển khai chức năng mong muốn của chúng tôi
- Tài khoản Vercel để triển khai dự án của bạn
Đây là gì?
Bạn có muốn bắt đầu sử dụng máy học để tạo hình ảnh từ các mô hình có sẵn trên Replica không? Chà, trong hướng dẫn này, chúng ta sẽ khám phá nhiều mô hình được lưu trữ của Replicate và Redis của Upstash. Chúng ta không chỉ khám phá những mô hình này mà còn hướng dẫn quy trình thiết lập một mô hình và đề cập đến cách bạn có thể dễ dàng cập nhật quá trình triển khai để sử dụng các mô hình khác.
Trong hướng dẫn này, chúng tôi sẽ đề cập đến cách sử dụng mô hình Đưa những bức ảnh cũ trở lại cuộc sống của Microsoft, về cơ bản là chụp một bức ảnh cũ, chạy nó qua mô hình và tạo ra một phiên bản đã chỉnh sửa và hy vọng sẽ được cải thiện cho bức ảnh của bạn.

Kiến trúc là gì?
Nếu có một số kinh nghiệm về React, bạn sẽ có thể xác định cách hoạt động của kiến trúc ứng dụng chỉ bằng cách đọc qua cơ sở mã, nhưng để làm cho việc đó dễ dàng hơn một chút hoặc nếu bạn chỉ muốn xem tổng quan thì có một cái được cung cấp bên dưới.

Tôi cần bắt đầu những gì?
Để bắt đầu, tất nhiên bạn sẽ cần một dự án Next.js. Bạn có thể thực hiện việc này bằng cách làm theo hướng dẫn thiết lập Next.js tại đây hoặc nếu bạn đã thiết lập sẵn thì cũng không sao. Trong hướng dẫn này, chúng tôi cũng sử dụng CSS Tailwind nhưng tất nhiên bạn có thể sử dụng bất kỳ hình thức thiết lập kiểu nào mà bạn thích.
Bây giờ chúng ta đã thiết lập dự án Next.js cơ bản, chúng ta vẫn có thể sử dụng thư viện Redis của Upstash bằng cách chạy lệnh:
npm install @upstash/redis
Tiếp theo, chúng ta sẽ điền .env.local của mình vào tệp có các khóa sau, trong đó có thể tìm thấy mã thông báo Redis trong bảng điều khiển Upstash, mã thông báo API sao chép ở đây trong tài khoản của bạn và URL trang web của bạn sẽ ở bất kỳ nơi nào bạn triển khai nó, vì vậy trong trường hợp này nó sẽ là điểm cuối triển khai Vercel.
SITE_URL=https://your-project-url.vercel.app
REPLICATE_API_TOKEN=
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN= Thiết lập biểu mẫu giao diện người dùng
Để bắt đầu, chúng ta cần một biểu mẫu xử lý biểu mẫu, bỏ phiếu và hiển thị các hình ảnh đã hoàn thành.
Khôi phục việc tạo biểu mẫu hình ảnh
Tập tin:pages/index.tsx
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>
</>
);
}
Theo mặc định, thành phần này hiển thị một biểu mẫu cho phép người dùng nhập URL hình ảnh của hình ảnh mà họ muốn khôi phục và một số tùy chọn đi kèm với nó, chẳng hạn như hình ảnh có độ phân giải cao hay hình ảnh có vết xước cần phải loại bỏ hay không. Khi người dùng điền thông tin này và gửi biểu mẫu, nó sẽ gửi POST yêu cầu tới /api/create cùng với dữ liệu biểu mẫu.
Khi yêu cầu này đã được gửi tới API và nhận được phản hồi cùng với thông tin dự đoán đã được trả về, thành phần sẽ chuyển sang trạng thái thăm dò và kiểm tra sẽ gửi GET yêu cầu tới /api/poll một lần mỗi giây để kiểm tra xem dự đoán đã hoàn thành chưa. Sau khi yêu cầu thăm dò trả về phản hồi thành công, cho biết rằng Replicate đã gửi yêu cầu đến điểm cuối gọi lại của chúng tôi, giờ đây chúng tôi sẽ có quyền truy cập vào kết quả dự đoán.
Trong khi cuộc bỏ phiếu đang diễn ra, biểu mẫu sẽ hiển thị một nút có tùy chọn hủy dự đoán. Sau khi được nhấn, thao tác này sẽ gửi POST yêu cầu tới /api/cancel với cancel_url từ dữ liệu dự đoán mà chúng tôi nhận được khi tạo lần đầu.
Việc triển khai bỏ phiếu sử dụng một móc tùy chỉnh được đặt trong hooks/useInterval.ts điều này cho phép chúng tôi làm việc dễ dàng và liền mạch với lối sống thành phần của React, đồng thời cung cấp một cách thuận tiện hơn để xử lý các khoảng thời gian có lệnh gọi lại trong bất kỳ thành phần React nhất định nào. Bạn có thể đọc thêm về hook này tại đây và tại đây nếu bạn muốn tìm hiểu thêm về nó và chi tiết hơn.
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; Thiết lập API
Thiết lập API, bao gồm một vài tệp, cho phép chúng tôi tạo và hủy dự đoán, thăm dò ý kiến để kiểm tra khi dự đoán hoàn tất, cũng như chỉ định lệnh gọi lại mà Bản sao sẽ sử dụng khi dự đoán hoàn tất.
Tạo dự đoán hình ảnh
Tập tin:pages/api/create.ts
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 });
});
}
Đối với điểm cuối tạo API, trước tiên chúng tôi thực hiện một bước kiểm tra đơn giản để đảm bảo rằng phương thức yêu cầu đến là POST yêu cầu và nếu không, chúng tôi sẽ trả về phản hồi 400 đơn giản. Sau đó chúng tôi tiến hành gửi POST yêu cầu Sao chép bằng mã thông báo API Sao chép của chúng tôi. Phần thân yêu cầu bao gồm các tham số cho mô hình đã cho version cho biết chúng tôi đang gửi yêu cầu đến mô hình nào (điều này được tìm thấy trong tab "API" trên mô hình bạn muốn sử dụng). Chúng tôi cũng chuyển qua các tham số được liên kết với mô hình với dữ liệu từ biểu mẫu ở giao diện người dùng.
Khi yêu cầu đã được gửi, chúng tôi sử dụng dự đoán được trả về id để lưu trữ nó trong Redis và trả về dữ liệu dự đoán cho giao diện người dùng để sử dụng trong việc thăm dò mục Redis cho đến khi nó bao gồm một dự đoán hoàn chỉnh.
Gọi lại
Tập tin:pages/api/callback.ts
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 });
}
} Điểm cuối gọi lại là điểm mà Replicate sẽ gửi POST yêu cầu để cho chúng tôi biết rằng quá trình xử lý một dự đoán nhất định đã kết thúc. Khi nhận được yêu cầu này, chúng tôi truy xuất dữ liệu dự đoán từ nội dung yêu cầu và cập nhật mục Redis đã cho với dữ liệu dự đoán đã hoàn thành.
Bỏ phiếu
Tập tin:pages/api/poll.ts
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 });
}
}
Để thiết lập bỏ phiếu, chúng tôi trích xuất id từ yêu cầu rồi cố gắng truy xuất dữ liệu được lưu trữ trong Redis theo mã nhận dạng đó và nếu không tìm thấy dữ liệu nào, chúng tôi sẽ trả về phản hồi 404, nhưng nếu có dữ liệu, chúng tôi sẽ trả về dữ liệu đó như một phần của phản hồi 200.
Hủy
Tập tin:pages/api/cancel.tsx
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 });
});
}
Điểm cuối API để hủy các dự đoán đã được bắt đầu khá đơn giản. Chúng tôi chỉ cần trích xuất cancel_url được truyền từ giao diện người dùng, bản thân nó xuất phát từ dự đoán được lưu trữ khi yêu cầu tạo được gửi và chúng tôi chỉ cần gửi POST yêu cầu tới điểm cuối đó, cùng với mã thông báo API Sao chép của chúng tôi.
Lib
Đối với lib của chúng tôi, chúng tôi sẽ tạo ứng dụng khách Redis được sử dụng để theo dõi
Tập tin:lib/redis.ts
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; Đối tượng này sẽ được sử dụng trong ứng dụng để lưu trữ và truy xuất dữ liệu trong khi đang thăm dò ý kiến, để chúng tôi biết khi nào quá trình hoàn thành webhook từ Replicate đã diễn ra.
Kết luận
Bản sao có sẵn nhiều mô hình khác nhau có thể được sử dụng thông qua API. Với Vercel và Upstash, việc sử dụng các mô hình máy học và triển khai các ứng dụng web hữu ích trở nên dễ dàng hơn bao giờ hết.
Nếu bạn muốn xem toàn bộ kho lưu trữ, bạn có thể truy cập vào đây.
Phát triển hơn nữa
Đây chỉ là một ví dụ đơn giản về việc sử dụng một mô hình khá đơn giản với Replica. Chỉ cần chuyển đổi tham số biểu mẫu và phiên bản trong API, bạn có thể dễ dàng thay đổi sang mô hình khác miễn là bạn đã liên kết mã thông báo API Sao chép, bạn sẽ có thể sử dụng bất kỳ mô hình nào có sẵn.
Bạn có thể khám phá tất cả các mô hình có sẵn của Replica tại đây và khi tìm thấy mô hình bạn muốn thử nghiệm, bạn có thể nhấp vào tab "API" để xem cách sử dụng mô hình đó. Tại đây, bạn cũng sẽ tìm thấy các nút dành cho Python, cURL, Cog và Docker. Các nút này cho phép bạn kiểm tra mô hình nhưng cũng hữu ích khi biết những tham số nào là bắt buộc và cách chúng được gửi.