AI আরও অ্যাক্সেসযোগ্য হওয়ার সাথে সাথে, রেপ্লিকেটের মতো কোম্পানিগুলি নির্বিঘ্নে প্রকল্পগুলিতে মেশিন লার্নিং মডেলগুলিকে একীভূত করা সহজ করে তুলেছে৷
এই নিবন্ধে, আমি কীভাবে CaptionAI তৈরি করেছি তা নিয়ে আলোচনা করতে যাচ্ছি, একটি ওয়েব অ্যাপ্লিকেশন যা ব্যবহারকারীদের একটি ছবি আপলোড করতে এবং একটি AI জেনারেটেড টেক্সট ক্যাপশন পেতে দেয়৷ আমি এই Vercel টেমপ্লেটটি ব্যবহার করে এই প্রকল্পটি তৈরি করেছি৷ এই ভিডিওটি কীভাবে এই প্রকল্পটি তৈরি করা হয়েছিল তা ব্যাখ্যা করে৷

আমরা কি ব্যবহার করব
- Next.js 13 (ফ্রন্ট-এন্ড এবং ব্যাক-এন্ড)
- Upstash Redis (রেট লিমিটিং)
- প্রতিলিপি (মেশিন লার্নিং API)
- টেইলউইন্ড সিএসএস (স্টাইলিং)
- ভারসেল (নিয়োজন)
আপনার যা প্রয়োজন
- ডাটাবেস তৈরি করার জন্য একটি Upstash অ্যাকাউন্ট
- মেশিন লার্নিং API অ্যাক্সেস করার জন্য একটি প্রতিলিপি অ্যাকাউন্ট
Upstash Redis সেট আপ করা হচ্ছে
একবার আপনি একটি Upstash অ্যাকাউন্ট তৈরি করেছেন এবং লগ ইন করলে আপনি Redis ট্যাবে যান এবং একটি ডাটাবেস তৈরি করতে যাচ্ছেন৷


আপনি আপনার ডাটাবেস তৈরি করার পরে, আপনি তারপরে বিশদ ট্যাবে যাচ্ছেন। আপনি REST API বিভাগটি খুঁজে না পাওয়া পর্যন্ত নিচে স্ক্রোল করুন এবং .env বোতামটি নির্বাচন করুন। বিষয়বস্তু অনুলিপি করুন এবং নিরাপদ কোথাও সংরক্ষণ করুন।

প্রতিলিপি সেট আপ করা হচ্ছে
একবার আপনি একটি প্রতিলিপি অ্যাকাউন্ট তৈরি করেছেন এবং লগ ইন করলে, আপনি অ্যাকাউন্ট ট্যাবে যেতে চলেছেন এবং API টোকেনটি নিরাপদ কোথাও সংরক্ষণ করতে চলেছেন৷
*দ্রষ্টব্য:আপনি বিনামূল্যে প্রতিলিপি ব্যবহার করতে পারেন, কিন্তু কিছুক্ষণ পরে আপনাকে আপনার ক্রেডিট কার্ড প্রবেশ করতে বলা হবে। আপনি যে মডেলটি ব্যবহার করেন তার উপর নির্ভর করে দাম পরিবর্তিত হয়। আমরা যে মডেলটি ব্যবহার করছি, সেলসফোর্স/ব্লিপ, চালানোর জন্য প্রায় $0.00042 খরচ হয়৷

প্রকল্প সেট আপ করা হচ্ছে
স্ক্র্যাচ থেকে একটি প্রকল্প তৈরি করার পরিবর্তে, আপনি GitHub থেকে সংগ্রহস্থল ক্লোন করতে পারেন।
একবার আপনি রেপো ক্লোন করার পরে, আপনি একটি .env ফাইল তৈরি করতে যাচ্ছেন। .example.env ফাইল থেকে .env ফাইলে তথ্য অনুলিপি করুন। একবার আপনি এটি কপি হয়ে গেলে, আপনি উপরের বিভাগগুলি থেকে আমরা যে আইটেমগুলি সংরক্ষণ করেছি তা যোগ করতে যাচ্ছেন।
এটি দেখতে এইরকম কিছু হওয়া উচিত:
// .env
REPLICATE_API_KEY="your_replicate_api_key_from_above"
// Optional, if you're doing rate limiting
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-তে প্রতিক্রিয়া পাঠ্য প্রদর্শন করে।

রিডিস ইনস্ট্যান্স তৈরি করুন
প্রকল্পের অভ্যন্তরে, আমরা আমাদের আপস্ট্যাশ রেডিস ক্লায়েন্ট সেট আপ করতে যাচ্ছি যেটি আমরা প্রয়োজনে পুরো প্রকল্প জুড়ে উল্লেখ করতে পারি।
// `/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", // Primary buttons & links
error: "#d23f4d", // Error messages
shade100: "#fff", // Standard text
shade200: "#fffe", // Secondary button text
shade300: "#fffd", // Secondary button text (hover)
shade400: "#fffc", // Welcome text
shade500: "#fff9", // Modal close button
shade600: "#fff7", // Border
shade700: "#fff2", // Progress indicator background
shade800: "#fff1", // File item background
shade900: "#ffff", // Various (draggable crop buttons, etc.)
},
},
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";
},
}; এই কোডটি একটি আপলোডার উপাদানের জন্য কনফিগারেশন বিকল্প সেট করে। আপলোডারটি আপলোডার() ফাংশন ব্যবহার করে তৈরি করা হয় এবং বিকল্পগুলি এটিতে একটি বস্তু হিসাবে পাস করা হয়।
প্রথম কনফিগারেশন বিকল্পটি হল apiKey যা আপলোডার পরিষেবার সাথে প্রমাণীকরণ করতে ব্যবহৃত হয়। পরিবেশ পরিবর্তনশীল NEXT_PUBLIC_UPLOAD_API_KEY সেট করা আছে কিনা তার উপর ভিত্তি করে apiKey-এর মান নির্ধারণ করা হয়। যদি এটি সেট করা হয়, তাহলে এনভায়রনমেন্ট ভেরিয়েবলের মান ব্যবহার করা হয়, অন্যথায়, "ফ্রি" মান ব্যবহার করা হয়।
অপশন অবজেক্টে আপলোডারের জন্য বিভিন্ন অপশন রয়েছে। এর মধ্যে রয়েছে:
- maxFileCount:সর্বোচ্চ সংখ্যক ফাইল সেট করে যা একবারে 1 এ আপলোড করা যেতে পারে।
- mimeTypes:আপলোড করা ফাইলগুলির জন্য অনুমোদিত MIME প্রকারগুলিকে "image/jpeg", "image/png", এবং "image/jpg" এ সেট করে।
- সম্পাদক:চিত্র সম্পাদকের জন্য বিকল্পগুলি কনফিগার করে, যা এই ক্ষেত্রে অক্ষম করা হয় ক্রপকে মিথ্যাতে সেট করে৷
- স্টাইল:আপলোডার UI এর জন্য কাস্টম শৈলী সংজ্ঞায়িত করে।
- onValidate:একটি ফাংশন সংজ্ঞায়িত করে যা প্রতিটি ফাইল আপলোড করার আগে যাচাই করার জন্য বলা হয়। এই ক্ষেত্রে, চিত্রটি কাজের জন্য নিরাপদ কিনা তা পরীক্ষা করতে ফাংশনটি একটি NSFWPredictor ব্যবহার করে। ছবিটি নিরাপদ না হলে, একটি ত্রুটি বার্তা ফিরে আসে যা নির্দেশ করে যে ছবিটি অনুমোদিত নয়৷ ৷
// `/pages/captions.tsx` continued
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!"); // set the button text to "Copied!" when text is copied
setTimeout(() => {
setButtonText("Copy"); // set the button text back to "Copy" after 2 seconds
}, 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 পদ্ধতি ব্যবহার করে। যখন পাঠ্যটি অনুলিপি করা হয়, তখন এটি বোতাম পাঠ্য ভেরিয়েবলকে "কপি করা হয়েছে!" "কপি" এ রিসেট করার আগে দুই সেকেন্ডের জন্য।
UploadDropZone নামে একটি সাব-কম্পোনেন্ট রয়েছে যা নির্দিষ্ট আপলোডার এবং বিকল্পগুলির সাথে আপলোডড্রপজোন উপাদানের একটি উদাহরণ রেন্ডার করে। onUpdate কলব্যাক আপলোড করা ছবির URL এবং জেনারেট করা ক্যাপশন সহ আসল ফটো এবং ক্যাপশন ভেরিয়েবল আপডেট করতে ব্যবহৃত হয়৷
অবশেষে, generateCaption নামে একটি অ্যাসিঙ্ক্রোনাস ফাংশন রয়েছে যা একটি fileUrl প্যারামিটারে নেয়, যা আপলোড করা ছবির URL। এটি একটি POST অনুরোধের সাথে /api/জেনারেট এন্ডপয়েন্টে কল করার জন্য ফেচ ব্যবহার করে এবং JSON পেলোড হিসাবে fileUrl এ পাস করে। প্রতিক্রিয়াটি তারপর JSON হিসাবে পার্স করা হয় এবং প্রতিক্রিয়া সফল হয়েছে কিনা তার উপর নির্ভর করে ক্যাপশন বা ত্রুটি পরিবর্তনশীল সেট করে। অনুরোধটি এখনও চলছে কিনা তা নির্দেশ করতে লোডিং ভেরিয়েবলটি আপডেট করা হয়েছে। এপিআই রেট সীমা আঘাত এড়াতে সেটটাইমআউট ফাংশন ব্যবহার করে ফাংশনটিতে 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;
};
}
// Create a new ratelimiter, that allows 3 requests every 15 minutes
const ratelimit = redis
? new Ratelimit({
redis: redis,
limiter: Ratelimit.fixedWindow(5, "1440 m"),
analytics: true,
})
: undefined;
... এই কোডটি Next.js-এ একটি API এন্ডপয়েন্ট তৈরি করার জন্য প্রয়োজনীয় মডিউল এবং প্রকারগুলি আমদানি করে, সেইসাথে একটি Upstash Redis ডাটাবেস ক্লায়েন্ট এবং "@upstash/ratelimit" নামক arate limiter লাইব্রেরি।
রেটলিমিট কনস্ট্যান্ট রেটেলিমিট ক্লাসের একটি নতুন উদাহরণ তৈরি করে, যা একটি ফিক্সড-উইন্ডো রেটলিমিটার তৈরি করে যা প্রতি 1440 মিনিটে (24 ঘন্টা) 5টি অনুরোধের অনুমতি দেয়। রেডিস প্রপার্টি রেটেলিমিট কনস্ট্রাক্টরের কাছে একটি প্যারামিটার হিসাবে পাস করা হয় যাতে আবেদনের একাধিক উদাহরণ জুড়ে হার সীমিত করা যায়। যদি রেডিস অনির্ধারিত হয় (যেমন, যদি রেডিস ডাটাবেস কনফিগার করা না থাকে), রেটসীমাও অনির্ধারিত হিসাবে সেট করা হয়। এর মানে হল রেডিস উপলব্ধ না হলে রেট সীমিতকরণ প্রয়োগ করা হবে না৷
৷// `/pages/api/generate.ts` continued
export default async function handler(
req: ExtendedNextApiRequest,
res: NextApiResponse<Data>
) {
// Rate Limiter Code
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;
}
}
... এই কোড ব্লকটি একটি এপিআই হ্যান্ডলার ফাংশনের অংশ যা একটি ক্লায়েন্ট এপিআই-তে করতে পারে এমন অনুরোধের হারকে সীমাবদ্ধ করে। এটি প্রথমে একটি রেট লিমিটার ইনস্ট্যান্স উপলব্ধ কিনা তা পরীক্ষা করে এবং যদি তাই হয়, অনুরোধ-আইপি প্যাকেজ ব্যবহার করে ক্লায়েন্টের আইপি ঠিকানা বের করে এবং এটি ratelimit.limit পদ্ধতিতে পাস করে। এই পদ্ধতিটি নির্দিষ্ট সময়সীমার মধ্যে থাকা অনুরোধের সংখ্যা এবং অনুরোধটি সফল হয়েছে কিনা তা সমন্বিত একটি বস্তু ফেরত দেয়।
অনুরোধ সফল হলে, X-RateLimit-Limitand X-RateLimit-বাকি হেডারগুলি প্রতিক্রিয়াতে সেট করা হয়। অনুরোধের সীমা অতিক্রম করা হলে, প্রতিক্রিয়ায় একটি 429 স্ট্যাটাস কোড এবং একটি ত্রুটি বার্তা পাঠানো হয়, এবং ফাংশনটি আরও কার্যকর হওয়া রোধ করতে তাড়াতাড়ি ফিরে আসে৷
BLIP ML API
// `/pages/api/generate.ts` continued
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 নেয় এবং image_captioning টাস্ক ব্যবহার করে ছবির ক্যাপশন পেতে "https://api.replicate.com/v1/predictions" এন্ডপয়েন্টে একটি POST অনুরোধ পাঠায়। অনুরোধটিতে অনুমোদনের শিরোনাম রয়েছে যাতে প্রমাণীকরণের জন্য প্রতিলিপি API কী রয়েছে এবং "সামগ্রী/অ্যাপ" শিরোনাম-এ সেট করা হয়েছে। API থেকে প্রতিক্রিয়া JSON হিসাবে পার্স করা হয়, এবং endpointUrl jsonStartResponse অবজেক্ট থেকে বের করা হয়।
আপনি যে মডেলটি ব্যবহার করতে চান সেটি নির্বাচন করে আপনি মডেলটির সংস্করণ নম্বর খুঁজে পেতে পারেন৷

API ট্যাব নির্বাচন করুন৷
৷

লাল রঙে রূপরেখা, সংস্করণ নম্বরটি প্রদর্শিত না হওয়া পর্যন্ত নীচে স্ক্রোল করুন৷ এবং নীল রঙে রূপরেখা দেওয়া হল ইনপুট পরামিতিগুলি আপনি ব্যবহার করতে পারেন৷ 
// `/pages/api/generate.ts` continued
let jsonStartResponse = await startResponse.json();
let endpointUrl = jsonStartResponse.urls.get;
// GET request to get the status of the image restoration process & return the result when it's ready
let caption: string | null = null;
while (!caption) {
// Loop in 1s intervals until the alt text is ready
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 পোল করতে একটি while লুপ ব্যবহার করা হয়। লুপ একই অনুমোদন এবং বিষয়বস্তু-প্রকার শিরোনাম সহ endpointUrl-এ একটি GET অনুরোধ পাঠায় এবং প্রতিক্রিয়াটি JSON হিসাবে পার্স করা হয়। jsonFinalResponse অবজেক্টের স্থিতি "সফল" হলে ক্যাপশনটি আউটপুট সম্পত্তি থেকে বের করা হয়। স্ট্যাটাস "ব্যর্থ" হলে, লুপটি বন্ধ হয়ে যায়। যদি স্ট্যাটাসটি "সফল" বা "ব্যর্থ" না হয়, তাহলে আবার ভোট দেওয়ার আগে লুপ সেটটাইমআউট পদ্ধতি ব্যবহার করে 1 সেকেন্ডের জন্য অপেক্ষা করে৷
অবশেষে, ক্যাপশনটি 200 এর স্ট্যাটাস কোড সহ JSON প্রতিক্রিয়া হিসাবে ফেরত দেওয়া হয় যদি এটি শূন্য না হয়, অন্যথায়, "ক্যাপশন তৈরি করতে ব্যর্থ" বার্তা সহ একটি প্রতিক্রিয়া 200 এর স্ট্যাটাস কোডের সাথে ফেরত দেওয়া হয়।
উপসংহার
উপসংহারে, এই প্রকল্পটি ইমেজ আপলোডিং, রেট সীমিতকরণ এবং মেশিন লার্নিং APIগুলিকে একীভূত করার ক্ষেত্রে মূল্যবান অভিজ্ঞতা প্রদান করেছে৷ এই প্রকল্পটি সফলভাবে সম্পূর্ণ করার মাধ্যমে, আমরা এই প্রযুক্তিগুলি সম্পর্কে আরও ভালভাবে বুঝতে পেরেছি এবং ভবিষ্যতে আরও উন্নত প্রকল্প তৈরি করতে কীভাবে সেগুলি ব্যবহার করা যেতে পারে৷