AI के अधिक सुलभ होने के साथ, Replicate जैसी कंपनियों ने मशीन लर्निंग मॉडल को प्रोजेक्ट में सहजता से इंटीग्रेट करना आसान बना दिया है।
इस लेख में, मैं चर्चा करूंगा कि कैसे मैंने CaptionAI बनाया, एक वेब एप्लिकेशन जो यूजर्स को एक इमेज अपलोड करने और AI-जनित टेक्स्ट कैप्शन प्राप्त करने की अनुमति देता है। मैंने इस प्रोजेक्ट को इस Vercel टेम्पलेट का उपयोग करके बनाया है। यह वीडियो भी है जो बताता है कि यह प्रोजेक्ट कैसे बनाया गया था।

हम क्या उपयोग करेंगे
- Next.js 13 (फ्रंट-एंड और बैक-एंड)
- Upstash Redis (रेट लिमिटिंग)
- Replicate (मशीन लर्निंग API)
- Tailwind CSS (स्टाइलिंग)
- Vercel (डेप्लॉयमेंट)
आपको क्या चाहिए
- डेटाबेस बनाने के लिए एक Upstash अकाउंट
- मशीन लर्निंग API तक पहुंच के लिए एक Replicate अकाउंट
Upstash Redis सेटअप करना
एक बार जब आप एक Upstash अकाउंट बना लेते हैं और लॉग इन हो जाते हैं, तो आप Redis टैब पर जाएंगे और एक डेटाबेस बनाएंगे।


अपना डेटाबेस बनाने के बाद, आप Details टैब पर जाएंगे। नीचे स्क्रॉल करें जब तक कि आपको REST API सेक्शन न मिल जाए और .env बटन चुनें। सामग्री को कॉपी करें और इसे कहीं सुरक्षित रखें।

Replicate सेटअप करना
एक बार जब आप एक Replicate अकाउंट बना लेते हैं और लॉग इन हो जाते हैं, तो आप Account टैब पर जाएंगे और API टोकन को कहीं सुरक्षित रखेंगे।
नोट: आप Replicate का मुफ्त में उपयोग कर सकते हैं, लेकिन थोड़ी देर बाद आपसे आपका क्रेडिट कार्ड दर्ज करने के लिए कहा जाएगा। कीमत आपके द्वारा उपयोग किए जाने वाले मॉडल के आधार पर भिन्न होती है। हम जिस मॉडल का उपयोग कर रहे हैं, salesforce/blip, उसे चलाने में लगभग $0.00042 का खर्च आता है।

प्रोजेक्ट सेटअप करना
स्क्रैच से प्रोजेक्ट बनाने के बजाय, आप GitHub से रिपॉजिटरी क्लोन कर सकते हैं।
एक बार जब आप रेपो क्लोन कर लेते हैं, तो आप एक .env फाइल बनाएंगे। .example.env फाइल से जानकारी को .env फाइल में कॉपी करें। एक बार जब आप इसे कॉपी कर लेते हैं, तो आप ऊपर के अनुभागों से सेव की गई वस्तुओं को जोड़ेंगे।
यह कुछ इस तरह दिखना चाहिए:
// .env
REPLICATE_API_KEY="your_replicate_api_key_from_above"
// वैकल्पिक, यदि आप रेट लिमिटिंग कर रहे हैं
UPSTASH_REDIS_REST_URL="your_upstash_redis_rest__url_from_above"
UPSTASH_REDIS_REST_TOKEN="your_upstash_redis_rest__token_from_above"
एक बार जब आप यह जानकारी शामिल कर लेते हैं तो आप इन कमांड को टर्मिनल में दर्ज करके प्रोजेक्ट चला सकते हैं:
npm install
npm run dev
रिपॉजिटरी संरचना
यह प्रोजेक्ट के लिए मुख्य फोल्डर संरचना है। मैंने उन फाइलों को लाल रंग में घेरा है जिन पर इस पोस्ट में आगे चर्चा की जाएगी जो इमेज अपलोड करने, रेट लिमिटिंग और BLIP ML API को लागू करने से संबंधित हैं।

उच्च-स्तरीय डेटा फ्लो
यह एक उच्च-स्तरीय आरेख है कि डेटा कैसे प्रवाहित हो रहा है। हमारा इनपुट, यूजर-अपलोडेड इमेज, अपलोड कंपोनेंट से होकर, BLIP ML API प्रोसेसिंग के लिए बैकएंड पर जाता है और फिर UI में प्रतिक्रिया टेक्स्ट प्रदर्शित करता है।

Redis इंस्टेंस बनाना
प्रोजेक्ट के अंदर, हम अपने Upstash Redis क्लाइंट को सेटअप करने जा रहे हैं जिसे हम प्रोजेक्ट में आवश्यक होने पर संदर्भित कर सकते हैं।
// `/utils/redis.ts`
import { Redis } from "@upstash/redis";
const redis = !!process.env.UPSTASH_REDIS_REST_URL && !!process.env.UPSTASH_REDIS_REST_TOKEN
? new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
})
: undefined;
export default redis;
यह कोड स्निपेट "@upstash/redis" पैकेज से Redis मॉड्यूल आयात करता है और एक नया Redis इंस्टेंस बनाता है। इंस्टेंस को दो एनवायरनमेंट वेरिएबल, UPSTASH_REDIS_REST_URL और UPSTASH_REDIS_REST_TOKEN की उपस्थिति के आधार पर सशर्त रूप से बनाया जाता है।
यदि दोनों वेरिएबल परिभाषित हैं, तो निर्दिष्ट URL और टोकन के साथ एक नया Redis इंस्टेंस बनाया जाता है। यदि दोनों में से कोई भी या दोनों वेरिएबल अपरिभाषित हैं, तो redis वेरिएबल को अपरिभाषित पर सेट किया जाता है। अंत में, एप्लिकेशन के अन्य भागों में उपयोग के लिए मॉड्यूल से redis वेरिएबल को निर्यात किया जाता है।
इमेज अपलोड करना
// `/pages/captions.tsx`
const uploader = Uploader({
apiKey: !!process.env.NEXT_PUBLIC_UPLOAD_API_KEY
? process.env.NEXT_PUBLIC_UPLOAD_API_KEY
: "free",
});
const options = {
maxFileCount: 1,
mimeTypes: ["image/jpeg", "image/png", "image/jpg"],
editor: {
images: {
crop: false,
},
},
styles: {
colors: {
primary: "#5a5cd1",
error: "#d23f4d",
shade100: "#fff",
shade200: "#fffe",
shade300: "#fffd",
shade400: "#fffc",
shade500: "#fff9",
shade600: "#fff7",
shade700: "#fff2",
shade800: "#fff1",
shade900: "#ffff",
},
},
onValidate: async (file: File): Promise<undefined | string> => {
let isSafe = false;
try {
isSafe = await NSFWPredictor.isSafeImg(file);
if (!isSafe) va.track("NSFW Image blocked");
} catch (error) {
console.error("NSFW predictor threw an error", error);
}
return isSafe ? undefined : "Detected a NSFW image which is not allowed. If this was a mistake, please contact me at hosna.qasmei@gmail.com";
},
};
यह कोड अपलोडर कंपोनेंट के लिए कॉन्फिगरेशन विकल्प सेट करता है। अपलोडर को Uploader() फंक्शन का उपयोग करके बनाया जाता है और विकल्पों को एक ऑब्जेक्ट के रूप में इसमें पास किया जाता है।
पहला कॉन्फिगरेशन विकल्प apiKey है जिसका उपयोग अपलोडर सेवा के साथ प्रमाणित करने के लिए किया जाता है। apiKey का मान इस आधार पर निर्धारित किया जाता है कि एनवायरनमेंट वेरिएबल NEXT_PUBLIC_UPLOAD_API_KEY सेट है या नहीं। यदि यह सेट है, तो एनवायरनमेंट वेरिएबल का मान उपयोग किया जाता है, अन्यथा, मान "free" उपयोग किया जाता है।
options ऑब्जेक्ट में अपलोडर के लिए विभिन्न विकल्प होते हैं। इनमें शामिल हैं:
- maxFileCount: एक साथ अपलोड की जा सकने वाली फाइलों की अधिकतम संख्या को 1 पर सेट करता है।
- mimeTypes: अपलोड की गई फाइलों के लिए अनुमत MIME प्रकार को "image/jpeg", "image/png", और "image/jpg" पर सेट करता है।
- editor: इमेज एडिटर के लिए विकल्प कॉन्फिगर करता है, जिसे इस मामले में crop को false पर सेट करके अक्षम किया गया है।
- styles: अपलोडर UI के लिए कस्टम स्टाइल परिभाषित करता है।
- onValidate: एक फंक्शन परिभाषित करता है जिसे अपलोड करने से पहले प्रत्येक फाइल को मान्य करने के लिए कॉल किया जाता है। इस मामले में, फंक्शन NSFWPredictor का उपयोग करके जांचता है कि इमेज काम के लिए सुरक्षित है या नहीं। यदि इमेज सुरक्षित नहीं है, तो एक त्रुटि संदेश वापस किया जाता है जो इंगित करता है कि इमेज की अनुमति नहीं है।
// `/pages/captions.tsx` जारी
const Home: NextPage = () => {
const [originalPhoto, setOriginalPhoto] = useState<string | null>(null);
const [caption, setCaption] = useState<string | null>(null);
const [buttonText, setButtonText] = useState("Copy");
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const copyToClipboard = () => {
navigator.clipboard.writeText(caption!);
setButtonText("Copied!");
// टेक्स्ट कॉपी होने पर बटन टेक्स्ट को "Copied!" पर सेट करें
setTimeout(() => {
setButtonText("Copy");
// 2 सेकंड के बाद बटन टेक्स्ट को वापस "Copy" पर सेट करें
}, 2000);
};
const UploadDropZone = () => (
<UploadDropzone
uploader={uploader}
options={options}
onUpdate={(file) => {
if (file.length !== 0) {
setOriginalPhoto(file[0].fileUrl.replace("raw", "thumbnail"));
generateCaption(file[0].fileUrl.replace("raw", "thumbnail"));
}
}}
width="670px"
height="250px"
/>
);
async function generateCaption(fileUrl: string) {
await new Promise((resolve) => setTimeout(resolve, 500));
setLoading(true);
const res = await fetch("/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ imageUrl: fileUrl }),
});
let newCaption = await res.json();
if (res.status !== 200) {
setError(newCaption);
} else {
setCaption(newCaption);
}
setLoading(false);
}
...
useState हुक के साथ कई स्टेट वेरिएबल परिभाषित किए गए हैं।
originalPhotoएक स्ट्रिंग है जो अपलोड की गई इमेज के URL को दर्शाता है।captionएक स्ट्रिंग है जिसमें अपलोड की गई इमेज के लिए जनरेटेड कैप्शन होता है।buttonTextएक स्ट्रिंग है जो कॉपी बटन पर टेक्स्ट को दर्शाता है।loadingएक बूलियन है जो इंगित करता है कि कंपोनेंट वर्तमान में डेटा ला रहा है या नहीं।errorएक स्ट्रिंग है जिसमें कैप्शन जनरेशन प्रक्रिया के दौरान कोई त्रुटि होने पर त्रुटि संदेश होता है।
कंपोनेंट में copyToClipboard नामक एक फंक्शन है, जो कैप्शन वेरिएबल को क्लिपबोर्ड पर कॉपी करने के लिए navigator.clipboard.writeText मेथड का उपयोग करता है। जब टेक्स्ट कॉपी हो जाता है, तो यह buttonText वेरिएबल को दो सेकंड के लिए "Copied!" पर बदल देता है और फिर "Copy" पर रीसेट कर देता है।
UploadDropZone नामक एक सब-कंपोनेंट भी है जो निर्दिष्ट अपलोडर और विकल्पों के साथ UploadDropzone कंपोनेंट का एक इंस्टेंस रेंडर करता है। onUpdate कॉलबैक का उपयोग originalPhoto और कैप्शन वेरिएबल को अपलोड की गई इमेज के URL और जनरेटेड कैप्शन के साथ अपडेट करने के लिए किया जाता है।
अंत में, generateCaption नामक एक एसिंक्रोनस फंक्शन है जो fileUrl पैरामीटर लेता है, जो अपलोड की गई इमेज का URL है। यह /api/generate एंडपॉइंट को POST अनुरोध के साथ कॉल करने के लिए fetch का उपयोग करता है और JSON पेलोड के रूप में fileUrl पास करता है। प्रतिक्रिया को तब JSON के रूप में पार्स किया जाता है और प्रतिक्रिया सफल रही या नहीं, इस पर निर्भर करते हुए या तो कैप्शन या एरर वेरिएबल सेट करता है। लोडिंग वेरिएबल को भी यह इंगित करने के लिए अपडेट किया जाता है कि अनुरोध अभी भी प्रगति पर है या नहीं। फंक्शन में API रेट लिमिट से बचने के लिए setTimeout फंक्शन का उपयोग करके 500ms की देरी भी शामिल है।
रेट लिमिटिंग
// `/pages/api/generate.ts`
import redis from "../../utils/redis";
import requestIp from "request-ip";
import { Ratelimit } from "@upstash/ratelimit";
import type { NextApiRequest, NextApiResponse } from "next";
type Data = string;
interface ExtendedNextApiRequest extends NextApiRequest {
body: { imageUrl: string; };
}
// एक नया रेटलिमिटर बनाएं, जो हर 15 मिनट में 3 अनुरोध की अनुमति देता है
const ratelimit = redis
? new Ratelimit({
redis: redis,
limiter: Ratelimit.fixedWindow(5, "1440 m"),
analytics: true,
})
: undefined;
...
यह कोड Next.js में API एंडपॉइंट बनाने के लिए आवश्यक मॉड्यूल और प्रकार आयात करता है, साथ ही Upstash Redis डेटाबेस क्लाइंट और "@upstash/ratelimit" नामक एक रेट लिमिटर लाइब्रेरी।
ratelimit कॉन्स्टेंट Ratelimit क्लास का एक नया इंस्टेंस बनाता है, जो एक फिक्स्ड-विंडो रेट लिमिटर बनाता है जो हर 1440 मिनट (24 घंटे) में 5 अनुरोध की अनुमति देता है। redis प्रॉपर्टी को Ratelimit कंस्ट्रक्टर में पैरामीटर के रूप में पास किया जाता है ताकि एप्लिकेशन के कई इंस्टेंस में रेट लिमिटिंग सक्षम हो सके। यदि redis अपरिभाषित है (उदाहरण के लिए, यदि Redis डेटाबेस कॉन्फिगर नहीं है), तो ratelimit को भी अपरिभाषित पर सेट किया जाता है। इसका मतलब है कि यदि Redis उपलब्ध नहीं है तो रेट लिमिटिंग लागू नहीं की जाएगी।
// `/pages/api/generate.ts` जारी
export default async function handler(
req: ExtendedNextApiRequest,
res: NextApiResponse<Data>
) {
// रेट लिमिटर कोड
if (ratelimit) {
const identifier = requestIp.getClientIp(req);
const result = await ratelimit.limit(identifier!);
res.setHeader("X-RateLimit-Limit", result.limit);
res.setHeader("X-RateLimit-Remaining", result.remaining);
if (!result.success) {
res
.status(429)
.json("Too many uploads in 1 day. Please try again after 24 hours.");
return;
}
}
...
यह कोड ब्लॉक एक API हैंडलर फंक्शन का हिस्सा है जो एक क्लाइंट द्वारा API पर किए जा सकने वाले अनुरोधों की दर को सीमित करता है। यह पहले जांचता है कि क्या रेट लिमिटर इंस्टेंस उपलब्ध है और यदि हां, तो request-ip पैकेज का उपयोग करके क्लाइंट के IP एड्रेस को निकालता है और इसे ratelimit.limit मेथड में पास करता है। यह मेथड एक ऑब्जेक्ट लौटाता है जिसमें निर्दिष्ट समय सीमा के भीतर शेष अनुरोधों की संख्या और अनुरोध सफल रहा या नहीं, यह शामिल होता है।
यदि अनुरोध सफल होता है, तो X-RateLimit-Limit और X-RateLimit-Remaining हेडर प्रतिक्रिया में सेट किए जाते हैं। यदि अनुरोध सीमा पार हो गई है, तो प्रतिक्रिया में 429 स्टेटस कोड और एक त्रुटि संदेश भेजा जाता है, और फंक्शन आगे के निष्पादन को रोकने के लिए जल्दी वापस आ जाता है।
BLIP ML API
// `/pages/api/generate.ts` जारी
const imageUrl = req.body.imageUrl;
let startResponse = await fetch("https://api.replicate.com/v1/predictions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Token " + process.env.REPLICATE_API_KEY,
},
body: JSON.stringify({
version: "2e1dddc8621f72155f24cf2e0adbde548458d3cab9f00c0139eea840d0ac4746",
input: {
image: imageUrl,
task: "image_captioning",
},
}),
});
...
कोड का यह भाग अनुरोध बॉडी से imageUrl लेता है और "https://api.replicate.com/v1/predictions" एंडपॉइंट पर POST अनुरोध भेजता है ताकि image_captioning टास्क का उपयोग करके इमेज कैप्शन प्राप्त किया जा सके। अनुरोध में प्रमाणीकरण के लिए Replicate API कुंजी वाला Authorization हेडर शामिल होता है, और Content-Type हेडर को "application/json" पर सेट किया जाता है। API से प्रतिक्रिया को JSON के रूप में पार्स किया जाता है, और jsonStartResponse ऑब्जेक्ट से endpointUrl निकाला जाता है।
आप जिस मॉडल का उपयोग करना चाहते हैं उसे चुनकर मॉडल का वर्जन नंबर पा सकते हैं।

API टैब चुनें।

नीचे स्क्रॉल करें जब तक कि वर्जन नंबर दिखाई न दे, लाल रंग में आउटलाइन किया गया। और नीले रंग में आउटलाइन किए गए इनपुट पैरामीटर हैं जिनका आप उपयोग कर सकते हैं।

// `/pages/api/generate.ts` जारी
let jsonStartResponse = await startResponse.json();
let endpointUrl = jsonStartResponse.urls.get;
// इमेज रेस्टोरेशन प्रक्रिया की स्थिति प्राप्त करने और तैयार होने पर परिणाम वापस करने के लिए GET अनुरोध
let caption: string | null = null;
while (!caption) {
// 1s अंतराल पर तब तक लूप करें जब तक कि ऑल्ट टेक्स्ट तैयार न हो जाए
console.log("polling for result...");
let finalResponse = await fetch(endpointUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Token " + process.env.REPLICATE_API_KEY,
},
});
let jsonFinalResponse = await finalResponse.json();
if (jsonFinalResponse.status === "succeeded") {
caption = jsonFinalResponse.output;
} else if (jsonFinalResponse.status === "failed") {
break;
} else {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
res.status(200).json(caption ? caption : "Failed to generate caption");
}
इसके बाद, कैप्शन तैयार होने तक 1-सेकंड के अंतराल पर endpointUrl को पोल करने के लिए एक व्हाइल लूप का उपयोग किया जाता है। लूप समान Authorization और Content-Type हेडर के साथ endpointUrl पर GET अनुरोध भेजता है, और प्रतिक्रिया को भी JSON के रूप में पार्स किया जाता है। यदि jsonFinalResponse ऑब्जेक्ट में स्टेटस "succeeded" है, तो कैप्शन को आउटपुट प्रॉपर्टी से निकाला जाता है। यदि स्टेटस "failed" है, तो लूप समाप्त हो जाता है। यदि स्टेटस न तो "succeeded" है और न ही "failed", तो लूप setTimeout मेथड का उपयोग करके फिर से पोल करने से पहले 1 सेकंड प्रतीक्षा करता है।
अंत में, यदि कैप्शन null नहीं है तो 200 स्टेटस कोड के साथ JSON प्रतिक्रिया के रूप में कैप्शन वापस किया जाता है, अन्यथा, "Failed to generate caption" संदेश के साथ 200 स्टेटस कोड की प्रतिक्रिया वापस की जाती है।
निष्कर्ष
निष्कर्षतः, इस प्रोजेक्ट ने इमेज अपलोडिंग, रेट लिमिटिंग और मशीन लर्निंग API को इंटीग्रेट करने में मूल्यवान अनुभव प्रदान किया है। इस प्रोजेक्ट को सफलतापूर्वक पूरा करके, हमें इन तकनीकों की बेहतर समझ मिली है और भविष्य में अधिक उन्नत प्रोजेक्ट बनाने के लिए उनका उपयोग कैसे किया जा सकता है।