Vercel AI SDK के यूज़र इंटरफ़ेस (यूआई) और एआई एलिमेंट का इस्तेमाल, Prompt API के साथ करना

पब्लिश की गई तारीख: 16 जुलाई, 2026

Vercel AI SDK के साथ, Prompt API के बिल्ट-इन वर्शन का इस्तेमाल करें लेख में, आपने जनरेशन के चार मुख्य प्रिमिटिव के बारे में जाना. ये प्रिमिटिव @browser-ai/core की मदद से काम करते हैं. ये प्रिमिटिव हैं: generateText(), streamText(), हाइब्रिड कोड, और Output.object() का इस्तेमाल करके स्ट्रक्चर्ड आउटपुट. इस बार, हम एक ऐसा इंटरैक्टिव टूल बनाएंगे जो पूरी तरह से ब्राउज़र में काम करता है. यह टूल, स्ट्रीमिंग चैट का पूरा यूज़र इंटरफ़ेस (यूआई) है. इसमें Prompt API उपलब्ध न होने पर, क्लाउड मॉडल पर अपने-आप फ़ॉल बैक करने की सुविधा भी है.

हम क्या बना रहे हैं

हम React चैट इंटरफ़ेस बना रहे हैं, जो ये काम करता है:

  • मल्टी-टर्न बातचीत को स्ट्रीम करने के लिए, Vercel AI SDK के useChat हुक का इस्तेमाल करता है.
  • मॉडल लूप को ब्राउज़र में चलाता है. इसके लिए, बैकएंड सर्वर की ज़रूरत नहीं होती.
  • Prompt API उपलब्ध न होने पर, Gemini 2.5 Flash पर अपने-आप फ़ॉल बैक हो जाता है.
  • Assistant के जवाबों को GitHub-फ़्लेवर्ड मार्कडाउन के तौर पर रेंडर करता है. साथ ही, स्ट्रीमिंग के दौरान अधूरे टोकन को मैनेज करता है.
  • पहले टोकन का इंतज़ार करते समय, "सोच रहा है…" का शिमर दिखाता है.
  • नए मैसेज पर अपने-आप स्क्रोल करता है. साथ ही, ऊपर की ओर स्क्रोल करने पर, सबसे नीचे स्क्रोल करने का बटन दिखाता है.

अतिरिक्त डिपेंडेंसी

चैट यूज़र इंटरफ़ेस (यूआई) के लिए, ai, @browser-ai/core, और @ai-sdk/google के साथ-साथ, React, AI SDK के React बाइंडिंग, और कुछ मार्कडाउन पैकेज की ज़रूरत होती है:

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

यूज़र इंटरफ़ेस (यूआई) के लिए, Tailwind CSS, कुछ कॉम्पोनेंट यूटिलिटी, और आइकॉन के लिए Lucide भी जोड़ें:

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

Vite कॉन्फ़िगरेशन: मल्टी-एंट्री बिल्ड

प्रोजेक्ट में पहले से ही 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 का ऐसा ऐब्स्ट्रैक्शन है जो किसी भी मॉडल पर सिलसिलेवार बातचीत के लूप को मैनेज करता है. यह मॉडल और सिस्टम प्रॉम्प्ट लेता है. साथ ही, बातचीत को इंटरनली मैनेज करता है.

एजेंट को useChat से कनेक्ट करना

@ai-sdk/react पैकेज का useChat हुक आम तौर पर, एचटीटीपी एंडपॉइंट से बात करता है. ब्राउज़र-साइड अनुमान के लिए, 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' वाले हिस्सों से मतलब है. उपयोगकर्ता के मैसेज, दाईं ओर अलाइन किए गए बबल के तौर पर दिखते हैं. वहीं, Assistant के मैसेज, बाईं ओर अलाइन किए गए आइकॉन के साथ दिखते हैं:

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, एआई एलिमेंट हैं. ये shadcn-style सोर्स कॉम्पोनेंट हैं. इन्हें npm से इंस्टॉल करने के बजाय, अपने प्रोजेक्ट में कॉपी किया जाता है. Response react-markdown को remark-gfm के साथ रैप करता है. इससे GitHub-फ़्लेवर्ड मार्कडाउन (टेबल, टास्क लिस्ट, स्ट्राइकथ्रू) और harden-react-markdown का इस्तेमाल किया जा सकता है. साथ ही, एआई आउटपुट में मौजूद लिंक और इमेज को सैनिटाइज़ किया जा सकता है.

मैसेज स्ट्रीम होने के दौरान, parseIncompleteMarkdown प्रॉपर्टी true होती है. स्ट्रीमिंग के दौरान, मॉडल **bold लिख सकता है, लेकिन अभी तक ** को बंद नहीं कर सकता. इससे एक डैंगलिंग टोकन बनता है, जो लिटरल ऐस्टरिस्क के तौर पर रेंडर होता है. parseIncompleteMarkdown खुले हुए **, __, `, ~~ को बंद करता है. साथ ही, डैंगलिंग [ लिंक को छोटा करता है, ताकि हर इंक्रीमेंटल चंक पर रेंडर किया गया आउटपुट साफ़-सुथरा दिखे.

"सोच रहा है…" की स्थिति

मैसेज भेजने और पहला टोकन पाने के बीच, status है 'submitted'. इस दौरान, ऐप्लिकेशन ऐनिमेटेड शिमर दिखाता है:

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

`messages.at(-1)?.role !== 'assistant'` की शर्त, Assistant का मैसेज स्ट्रीम होने के बाद, शिमर को फिर से दिखने से रोकती है.messages.at(-1)?.role !== 'assistant'

ThinkingMessage कॉम्पोनेंट का इस्तेमाल करता है: यह <span> है, जिसमें मूविंग ग्रेडिएंट होता है. इससे "सोच रहा है…" टेक्स्ट को स्वीपिंग हाइलाइट इफ़ेक्ट मिलता है.Shimmerbackground-clip: text

अपने-आप स्क्रोल होने की सुविधा

नया कॉन्टेंट आने पर, ऐप्लिकेशन सबसे नीचे स्क्रोल करता है. हालांकि, यह सुविधा सिर्फ़ तब काम करती है, जब उपयोगकर्ता पहले से ही सबसे नीचे हो. अगर उपयोगकर्ता बातचीत के बीच में कुछ पढ़ रहा है, तो उसे स्क्रोल करके कहीं और ले जाना परेशान करने वाला हो सकता है.

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 की वैल्यू 'फ़ॉल्स' होने पर, सबसे नीचे स्क्रोल करने का फ़्लोटिंग बटन दिखता है. जब उपयोगकर्ता वापस सबसे नीचे आ जाता है, तो यह बटन फ़ेड आउट हो जाता है.

इनपुट एरिया

टेक्स्ट एरिया में टाइप करने पर, उसका साइज़ अपने-आप बदल जाता है. इसके लिए, हर इनपुट इवेंट पर उसकी ऊंचाई को auto पर रीसेट किया जाता है. इसके बाद, उसे scrollHeight पर सेट किया जाता है. यह Enter दबाने पर सबमिट होता है (Shift+Enter दबाने पर नहीं). साथ ही, जवाब स्ट्रीम होने के दौरान, 'भेजें' बटन की जगह 'रोकें' बटन दिखता है. यह बटन 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} />;
}

एजेंट के रिज़ॉल्व होने के बाद, स्पिनर गायब हो जाता है और चैट यूज़र इंटरफ़ेस (यूआई) माउंट हो जाता है. एजेंट के रिज़ॉल्व होने में, बिल्ट-इन मॉडल के तुरंत बूट होने या मॉडल डाउनलोड होने का इंतज़ार करने में लगने वाला समय शामिल है.

डेमो

लाइव डेमो, पूरी तरह से काम करने वाला चैटबॉट है. यह पूरी तरह से आपके ब्राउज़र में काम करता है. कोई मैसेज टाइप करें और Enter दबाएं. अगर Prompt API उपलब्ध है, तो जवाब सीधे उपयोगकर्ता के डिवाइस पर मौजूद मॉडल से स्ट्रीम होता है. इसके लिए, नेटवर्क का अनुरोध की ज़रूरत नहीं होती. अगर आपका ब्राउज़र, Prompt API के साथ काम नहीं करता है, तो यह अपने-आप Gemini 2.5 Flash पर फ़ॉल बैक हो जाता है. इससे किसी चीज़ को सूची में समझाने, कोड स्निपेट लिखने या मार्कडाउन फ़ॉर्मैटिंग का इस्तेमाल करने के लिए कहें. जवाब, फ़ॉर्मैट किए गए कोड ब्लॉक, टेबल, और इनलाइन कोड के साथ रेंडर होते हैं.

चैट इंटरफ़ेस में, एआई असिस्टेंट से हुई बातचीत को दिखाया गया है. इसमें टेक्स्ट डालने का फ़ील्ड और स्ट्रीमिंग रिस्पॉन्स एरिया दिखाया गया है.

नतीजा

इन दोनों लेखों में, आपने Vercel AI SDK की मदद से, ब्राउज़र के बिल्ट-इन Prompt API की पूरी रेंज देखी. इसमें, जनरेशन के रॉ प्रिमिटिव से लेकर, स्ट्रीमिंग चैट के बेहतर इंटरफ़ेस तक की जानकारी शामिल है.

Vercel AI SDK के साथ, Prompt API के बिल्ट-इन वर्शन का इस्तेमाल करें लेख में, आपने टेक्स्ट जनरेट करने के लिए generateText() और streamText() का इस्तेमाल करने का तरीका जाना. साथ ही, Output.object() की मदद से स्ट्रक्चर्ड JSON आउटपुट का अनुरोध करने और हाइब्रिड कोड लिखने का तरीका भी जाना. हाइब्रिड कोड, जनरेशन लॉजिक में कोई बदलाव किए बिना, रनटाइम पर बिल्ट-इन मॉडल और क्लाउड सेवा देने वाली कंपनी के मॉडल में से किसी एक को चुनता है.

इस लेख में, आपने उन्हीं बिल्डिंग ब्लॉक का इस्तेमाल किया है और उन्हें पूरे React यूज़र इंटरफ़ेस (यूआई) में रैप किया है. इसमें, बातचीत के लूप को मैनेज करने के लिए ToolLoopAgent, ब्राउज़र में सीधे जवाब स्ट्रीम करने के लिए DirectChatTransport के साथ useChat, और मार्कडाउन जवाबों को साफ़-सुथरे तरीके से रेंडर करने के लिए एआई एलिमेंट कॉम्पोनेंट शामिल हैं. साथ ही, Prompt API उपलब्ध न होने पर, क्लाउड मॉडल पर अपने-आप फ़ॉल बैक करने की सुविधा भी है.

इसका नतीजा, दो डेमो हैं. ये दोनों डेमो पूरी तरह से ब्राउज़र में काम करते हैं. इसके लिए, बैकएंड की ज़रूरत नहीं होती: