প্রম্পট এপিআই-এর সাথে ভার্সেল এআই এসডিকে ইউআই এবং এআই এলিমেন্টস ব্যবহার করুন

প্রকাশিত: ১৬ জুলাই, ২০২৬

"Use the built-in Prompt API with the Vercel AI SDK" অংশে, আপনি চারটি মূল জেনারেশন প্রিমিটিভ দেখেছেন, যথা generateText() , streamText() , হাইব্রিড কোড, এবং Output.object() ব্যবহার করে স্ট্রাকচার্ড আউটপুট, যেগুলোর সবই @browser-ai/core দ্বারা চালিত। এবার আপনি আরও বেশি ইন্টারেক্টিভ কিছু তৈরি করবেন: একটি সম্পূর্ণ স্ট্রিমিং চ্যাট UI যা পুরোপুরি ব্রাউজারে চলে এবং Prompt API উপলব্ধ না থাকলে স্বয়ংক্রিয়ভাবে একটি ক্লাউড মডেলে ফলব্যাক করে।

আপনি যা তৈরি করছেন

একটি রিয়্যাক্ট চ্যাট ইন্টারফেস যা নিম্নলিখিত কাজগুলো করে:

  • একাধিক পালায় কথোপকথন স্ট্রিমিং করার জন্য Vercel AI SDK-এর useChat হুক ব্যবহার করে।
  • কোনো ব্যাকএন্ড সার্ভারের প্রয়োজন ছাড়াই ব্রাউজারে মডেল লুপটি চালায়।
  • প্রম্পট এপিআই অনুপলব্ধ থাকলে স্বয়ংক্রিয়ভাবে জেমিনি ২.৫ ফ্ল্যাশে ফিরে যায়।
  • সহকারীর উত্তরগুলোকে গিটহাব-ধাঁচের মার্কডাউন হিসেবে রেন্ডার করে এবং স্ট্রিমিং চলাকালীন অসম্পূর্ণ টোকেনগুলোও সামাল দেয়।
  • প্রথম টোকেনটির জন্য অপেক্ষা করার সময় একটি "ভাবছি..." লেখা ঝিলিক দেখা যায়।
  • নতুন মেসেজে স্বয়ংক্রিয়ভাবে স্ক্রল হয়ে যায় এবং উপরে স্ক্রল করার পর নিচে যাওয়ার জন্য একটি বাটন থাকে।

অতিরিক্ত নির্ভরতা

ai , @browser-ai/core , এবং @ai-sdk/google এর পাশাপাশি, চ্যাট UI-এর জন্য React, AI SDK-এর React বাইন্ডিং এবং কয়েকটি Markdown প্যাকেজ প্রয়োজন:

npm install react react-dom @ai-sdk/react
npm install react-markdown remark-gfm harden-react-markdown
npm install -D @types/react @types/react-dom

UI-এর জন্য, এছাড়াও Tailwind CSS, কিছু কম্পোনেন্ট ইউটিলিটি এবং আইকনের জন্য Lucide যোগ করুন:

npm install -D tailwindcss postcss autoprefixer
npm install clsx tailwind-merge lucide-react

ভোট কনফিগারেশন: একাধিক এন্ট্রি বিল্ড

প্রজেক্টটিতে ইতিমধ্যেই index.html আছে। chat.html দ্বিতীয় এন্ট্রি পয়েন্ট হিসেবে যোগ করুন এবং উভয় ফাইল বিল্ড করার জন্য Vite কনফিগার করুন:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: { alias: { '@': resolve(__dirname, './src') } },
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        chat: resolve(__dirname, 'chat.html'),
      },
    },
  },
});

chat.html খুবই সংক্ষিপ্ত, এতে শুধু একটি <div id="root"> এবং একটি স্ক্রিপ্ট ট্যাগ রয়েছে:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Built-in AI Chatbot</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/chat.tsx"></script>
  </body>
</html>

স্বয়ংক্রিয় মডেল নির্বাচন

চ্যাটবটে, অ্যাপটি স্বয়ংক্রিয়ভাবে সেই সিদ্ধান্তটি নেয়: এটি প্রথমে বিল্ট-ইন মডেলটি চেষ্টা করে, এবং প্রম্পট এপিআই (Prompt API) অনুপলব্ধ থাকলে ক্লাউডে ফিরে যায়।

এটি মডিউল লোড হওয়ার সময়, React মাউন্ট হওয়ার আগেই, এই কাজটি করে, ফলে ব্যবহারকারী যখন তার প্রথম বার্তাটি টাইপ করেন, ততক্ষণে এজেন্টটি প্রস্তুত থাকে:

const agentPromise: Promise<ToolLoopAgent> = (async () => {
  const builtIn = browserAI();
  let model: any = builtIn;

  if (typeof builtIn.availability === 'function') {
    const availability = await builtIn.availability();
    if (availability === 'unavailable') {
      const { createGoogleGenerativeAI } = await import('@ai-sdk/google');
      model = createGoogleGenerativeAI({ apiKey })('gemini-2.5-flash');
    } else if (availability === 'downloadable') {
      await builtIn.createSessionWithProgress(() => {});
    }
  }

  return new ToolLoopAgent({ model, instructions: 'You are a helpful assistant.' });
})();

নতুন সংযোজনটি হলো ToolLoopAgent ); এটি একটি এআই এসডিকে অ্যাবস্ট্রাকশন যা যেকোনো মডেলের উপর একটি বহু-পর্যায়ের কথোপকথন চক্র পরিচালনা করে। এটি মডেল এবং একটি সিস্টেম প্রম্পট গ্রহণ করে এবং অভ্যন্তরীণভাবে কথোপকথনটি সামলায়।

এজেন্টকে চ্যাট ব্যবহার করার জন্য সংযুক্ত করুন

@ai-sdk/react প্যাকেজের useChat হুকটি সাধারণত একটি HTTP এন্ডপয়েন্টের সাথে যোগাযোগ করে। ব্রাউজার-সাইড ইনফারেন্সের জন্য, এর পরিবর্তে DirectChatTransport ব্যবহার করুন। এটি কোনো সার্ভারের সম্পৃক্ততা ছাড়াই ToolLoopAgent লুপটি সম্পূর্ণরূপে ব্রাউজারে চালায়:

const transport = useMemo(() => new DirectChatTransport({ agent }), [agent]);
const { messages, sendMessage, status, stop } = useChat({ transport });

useMemo গুরুত্বপূর্ণ, কারণ DirectChatTransport কথোপকথনের অবস্থা ধরে রাখে, তাই এটি একটি স্থিতিশীল রেফারেন্স হওয়া আবশ্যক। প্রতিবার রেন্ডার করার সময় এটি পুনরায় তৈরি করলে কথোপকথনটি রিসেট হয়ে যায়।

useChat আপনাকে দেয়:

  • Messages : সম্পূর্ণ কথোপকথনটি UIMessage[] হিসেবে, যার প্রতিটির একটি role এবং একটি parts অ্যারে থাকে।
  • sendMessage({ text }) : নতুন ব্যবহারকারীর পালা পাঠায় এবং প্রতিক্রিয়া স্ট্রিমিং শুরু করে
  • Status : 'idle' | 'submitted' | 'streaming' | 'error'
  • Stop : উড়ন্ত অবস্থায় জেনারেশন বাতিল করে।

বার্তা রেন্ডার করুন

প্রতিটি মেসেজের একটি parts অ্যারে থাকে। এই চ্যাটবটের জন্য, আমরা শুধুমাত্র type: 'text' পার্টসগুলো নিয়েই আগ্রহী। ব্যবহারকারীর মেসেজগুলো ডানদিকে সারিবদ্ধ একটি বাবল হিসেবে প্রদর্শিত হয়; অ্যাসিস্ট্যান্টের মেসেজগুলো একটি আইকনসহ বামদিকে সারিবদ্ধ থাকে।

const ChatMessage = ({ message, isStreaming }: { message: UIMessage; isStreaming: boolean }) => {
  const isUser = message.role === 'user';

  const textParts = message.parts.map((part, i) => {
    if (part.type !== 'text') return null;
    if (isUser) return <span key={i}>{part.text}</span>;
    return <Response key={i} parseIncompleteMarkdown={isStreaming}>{part.text}</Response>;
  });

  if (isUser) {
    return (
      <div className="flex flex-col items-end gap-2 animate-fade-up">
        <MessageContent className="w-fit max-w-[min(80%,56ch)] ...">
          {textParts}
        </MessageContent>
      </div>
    );
  }

  return (
    <div className="flex items-start gap-3">
      <AIIcon />
      <MessageContent className="text-[13px] leading-[1.65]">{textParts}</MessageContent>
    </div>
  );
};

MessageContent এবং Response হলো AI এলিমেন্ট । এগুলো shadcn- স্টাইলের সোর্স কম্পোনেন্ট, যা npm থেকে ইনস্টল করার পরিবর্তে আপনাকে আপনার প্রোজেক্টে কপি করতে হয়। Response react-markdown remark-gfm দিয়ে র‍্যাপ করে GitHub-এর মতো Markdown (টেবিল, টাস্ক লিস্ট, স্ট্রাইকথ্রু) তৈরি করে এবং harden-react-markdown দিয়ে AI আউটপুটে লিঙ্ক ও ছবি স্যানিটাইজ করে।

মেসেজটি স্ট্রিমিং চলাকালীন parseIncompleteMarkdown প্রপটি true থাকে। স্ট্রিমিং চলাকালীন, মডেলটি **bold লিখতে পারে কিন্তু ** বন্ধ নাও করতে পারে, ফলে একটি ড্যাংলিং টোকেন থেকে যায় যা আক্ষরিক অ্যাস্টারিস্ক (*) হিসাবে রেন্ডার হবে। parseIncompleteMarkdown যেকোনো খোলা ** , __ , ` , ~~ বন্ধ করে এবং ড্যাংলিং [ লিঙ্কের শুরু ছেঁটে ফেলে, ফলে প্রতিটি ইনক্রিমেন্টাল চাঙ্কে রেন্ডার করা আউটপুট পরিষ্কার থাকে।

"চিন্তা..." অবস্থা

মেসেজ পাঠানো এবং প্রথম টোকেন পাওয়ার মধ্যবর্তী সময়ে status 'submitted' থাকে। এই সময়কালে অ্যাপটি একটি অ্যানিমেটেড ঝিকিমিকি দেখায়:

{status === 'submitted' && messages.at(-1)?.role !== 'assistant' && (
  <ThinkingMessage />
)}

messages.at(-1)?.role !== 'assistant' শর্তটি, একবার অ্যাসিস্ট্যান্ট মেসেজ আসা শুরু হয়ে গেলে শিমারটিকে পুনরায় প্রদর্শিত হতে বাধা দেয়।

ThinkingMessage একটি Shimmer কম্পোনেন্ট ব্যবহার করে: একটি <span> যার মধ্যে background-clip: text ব্যবহার করে একটি চলমান গ্রেডিয়েন্ট রয়েছে, যা "Thinking…" লেখাটিকে একটি বিস্তৃত হাইলাইট এফেক্ট দেয়।

স্বয়ংক্রিয় স্ক্রোল

যখন নতুন কন্টেন্ট আসে, অ্যাপটি একদম নিচে স্ক্রল করে, কিন্তু শুধুমাত্র তখনই যখন ব্যবহারকারী ইতিমধ্যেই একদম নিচে থাকেন। কথোপকথনের মাঝখানে ব্যবহারকারী যা পড়ছেন, সেখান থেকে তাকে স্ক্রল করে সরিয়ে দেওয়াটা বিরক্তিকর হবে।

const [isAtBottom, setIsAtBottom] = useState(true);

useEffect(() => {
  if (isAtBottom) endRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages, status, isAtBottom]);

const handleScroll = () => {
  const el = containerRef.current;
  if (!el) return;
  setIsAtBottom(el.scrollHeight - el.scrollTop - el.clientHeight < 50);
};

যখন isAtBottom মান false হয়, তখন একটি ভাসমান স্ক্রল-টু-বটম বাটন দেখা যায় এবং ব্যবহারকারী আবার একদম নিচে ফিরে এলে সেটি ধীরে ধীরে অদৃশ্য হয়ে যায়।

ইনপুট এলাকা

আপনি টাইপ করার সাথে সাথে টেক্সটএরিয়াটি স্বয়ংক্রিয়ভাবে আকার পরিবর্তন করে। প্রতিটি ইনপুট ইভেন্টে এর উচ্চতা প্রথমে ' auto তে এবং তারপর ' scrollHeight এ রিসেট করা হয়। এটি Enter চাপলে সাবমিট হয় (Shift+Enter চাপলে নয়), এবং যখন কোনো প্রতিক্রিয়া স্ট্রিম হতে থাকে, তখন 'Send' বাটনটির জায়গায় একটি 'Stop' বাটন চলে আসে যা stop() ফাংশনটিকে কল করে।

<textarea
  onInput={(e) => {
    const el = e.currentTarget;
    el.style.height = 'auto';
    el.style.height = `${el.scrollHeight}px`;
  }}
  onKeyDown={(e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      if (input.trim() && !isStreaming) {
        sendMessage({ text: input });
        setInput('');
      }
    }
  }}
/>;
{
  isStreaming ? (
    <Button variant="outline" onClick={stop}>
      Stop
    </Button>
  ) : (
    <Button type="submit" disabled={!input.trim()}>
      Send
    </Button>
  );
}

লোডিং অবস্থায় মাউন্ট করুন

যেহেতু agentPromise অ্যাসিঙ্ক, তাই Chat রেন্ডার করার আগে এটির জন্য অপেক্ষা করুন। একটি App র‍্যাপার প্রমিসটি রিজলভ করে এবং এই সময়ের মধ্যে একটি স্পিনার দেখায়:

function App() {
  const [agent, setAgent] = (useState < ToolLoopAgent) | (null > null);

  useEffect(() => {
    agentPromise.then(setAgent);
  }, []);

  if (!agent) {
    return (
      <div className="flex h-dvh items-center justify-center">
        <Loader size={20} />
      </div>
    );
  }

  return <Chat agent={agent} />;
}

এজেন্টটি একবার রিজলভ হয়ে গেলে, সেটা বিল্ট-ইন মডেলের তাৎক্ষণিক বুটিং হোক বা মডেল ডাউনলোডের জন্য অপেক্ষা করা হোক, স্পিনারটি অদৃশ্য হয়ে যায় এবং চ্যাট UI মাউন্ট হয়।

ডেমো

লাইভ ডেমোটি হলো একটি সম্পূর্ণ কার্যকরী চ্যাটবট যা পুরোপুরি আপনার ব্রাউজারে চলে। একটি বার্তা টাইপ করুন এবং এন্টার চাপুন। যদি প্রম্পট এপিআই (Prompt API) উপলব্ধ থাকে, তাহলে কোনো নেটওয়ার্ক অনুরোধ ছাড়াই উত্তরটি সরাসরি ডিভাইসের মডেল থেকে স্ট্রিম হয়ে যায়। যদি আপনার ব্রাউজার প্রম্পট এপিআই সমর্থন না করে, তবে এটি স্বয়ংক্রিয়ভাবে জেমিনি ২.৫ ফ্ল্যাশ (Gemini 2.5 Flash)-এ ফিরে যায়। এটিকে একটি তালিকার কোনো কিছু ব্যাখ্যা করতে, একটি কোড স্নিপেট লিখতে, বা মার্কডাউন ফরম্যাটিং ব্যবহার করতে বলুন। এর উত্তরগুলো স্বয়ংক্রিয়ভাবে ফরম্যাট করা কোড ব্লক, টেবিল এবং ইনলাইন কোডসহ রেন্ডার হয়।

একটি চ্যাট ইন্টারফেস, যেখানে একটি এআই অ্যাসিস্ট্যান্টের সাথে কথোপকথন দেখানো হচ্ছে এবং এতে একটি টেক্সট ইনপুট ফিল্ড ও একটি স্ট্রিমিং রেসপন্স এরিয়া রয়েছে।

উপসংহার

এই দুটি আর্টিকেলের মাধ্যমে আপনারা দেখেছেন যে, ব্রাউজারের বিল্ট-ইন প্রম্পট এপিআই ব্যবহার করে ভার্সেল এআই এসডিকে কী কী করতে পারে—সাধারণ জেনারেশন প্রিমিটিভ থেকে শুরু করে একটি পরিশীলিত স্ট্রিমিং চ্যাট ইন্টারফেস পর্যন্ত।

"Use the built-in Prompt API with the Vercel AI SDK" অধ্যায়ে, আপনি শিখেছেন কীভাবে নন-স্ট্রিমিং এবং স্ট্রিমিং টেক্সট জেনারেশনের জন্য generateText()streamText() ব্যবহার করতে হয়, কীভাবে Output.object() দিয়ে স্ট্রাকচার্ড JSON আউটপুটের জন্য অনুরোধ করতে হয়, এবং কীভাবে এমন হাইব্রিড কোড লিখতে হয় যা জেনারেশন লজিকে কোনো পরিবর্তন না করেই রানটাইমে বিল্ট-ইন মডেল ও ক্লাউড প্রোভাইডারের মধ্যে যেকোনো একটিকে বেছে নেয়।

এই ডকুমেন্টে, আপনি সেই একই বিল্ডিং ব্লকগুলো নিয়ে সেগুলোকে একটি সম্পূর্ণ React UI-এর মধ্যে সাজিয়েছেন: কথোপকথনের চক্রটি পরিচালনা করার জন্য একটি ToolLoopAgent , সরাসরি ব্রাউজারে প্রতিক্রিয়া স্ট্রিম করার জন্য DirectChatTransport সহ useChat , এবং আসা মাত্রই Markdown প্রতিক্রিয়াগুলো পরিচ্ছন্নভাবে রেন্ডার করার জন্য AI Elements কম্পোনেন্ট; এই সবকিছুর সাথেই রয়েছে স্বয়ংক্রিয় ক্লাউড ফলব্যাকের ব্যবস্থা, যখন Prompt API উপলব্ধ থাকে না।

এর ফলে দুটি ডেমো তৈরি হয়েছে যা কোনো ব্যাকএন্ড ছাড়াই সম্পূর্ণভাবে ব্রাউজারে কাজ করে: