पब्लिश होने की तारीख: 16 जुलाई, 2026
Vercel AI SDK के साथ, बिल्ट-इन प्रॉम्प्ट एपीआई का इस्तेमाल करें में, आपने जनरेशन के चार मुख्य प्रिमिटिव देखे. ये हैं: generateText(), streamText(), हाइब्रिड कोड, और Output.object() का इस्तेमाल करके स्ट्रक्चर्ड आउटपुट. ये सभी @browser-ai/core की मदद से काम करते हैं. इस बार आपको ज़्यादा इंटरैक्टिव सुविधा बनानी है: एक ऐसा स्ट्रीमिंग चैट यूज़र इंटरफ़ेस (यूआई) जो पूरी तरह से ब्राउज़र में चलता है. साथ ही, Prompt API उपलब्ध न होने पर, क्लाउड मॉडल पर अपने-आप फ़ॉलबैक हो जाता है.
आपको क्या बनाना है
एक React चैट इंटरफ़ेस, जो ये काम करता है:
- यह Vercel AI SDK के
useChatहुक का इस्तेमाल करती है. इससे सिलसिलेवार बातचीत को स्ट्रीम किया जा सकता है. - यह मॉडल लूप को ब्राउज़र में चलाता है. इसके लिए, बैकएंड सर्वर की ज़रूरत नहीं होती.
- Prompt API के उपलब्ध न होने पर, यह अपने-आप Gemini 2.5 Flash पर वापस आ जाता है.
- यह कुकी, Assistant के जवाबों को GitHub-फ़्लेवर्ड मार्कडाउन के तौर पर रेंडर करती है. साथ ही, स्ट्रीमिंग के दौरान अधूरे टोकन को मैनेज करती है.
- पहले टोकन का इंतज़ार करते समय, "सोचा जा रहा है…" शाइन दिखाता है.
- नए मैसेज पर अपने-आप स्क्रोल होता है. ऊपर की ओर स्क्रोल करने पर, सबसे नीचे स्क्रोल करने का बटन दिखता है.
अन्य डिपेंडेंसी
चैट यूज़र इंटरफ़ेस (यूआई) के लिए, ai, @browser-ai/core, और @ai-sdk/google के साथ-साथ, React, एआई 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
यूज़र इंटरफ़ेस (यूआई) के लिए, 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 है. यह एआई एसडीके का ऐसा ऐब्स्ट्रैक्शन है जो किसी भी मॉडल के ऊपर, सिलसिलेवार बातचीत वाले लूप को मैनेज करता है. यह मॉडल और सिस्टम प्रॉम्प्ट लेता है. साथ ही, बातचीत को अंदरूनी तौर पर मैनेज करता है.
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-स्टाइल के सोर्स कॉम्पोनेंट होते हैं. इन्हें npm से इंस्टॉल करने के बजाय, अपने प्रोजेक्ट में कॉपी किया जाता है. Response को react-markdown के साथ रैप किया जाता है. GitHub-फ़्लेवर्ड मार्कडाउन (टेबल, टास्क की सूचियां, स्ट्राइकथ्रू) के लिए remark-gfm और एआई के आउटपुट में लिंक और इमेज को सुरक्षित करने के लिए harden-react-markdown का इस्तेमाल किया जाता है.
मैसेज स्ट्रीम होने के दौरान, parseIncompleteMarkdown प्रॉपर्टी true होती है. स्ट्रीमिंग के दौरान, मॉडल **bold लिख सकता है, लेकिन ** को अभी बंद नहीं करता है. इससे एक डैंगलिंग टोकन छूट जाता है, जो लिटरल ऐस्टरिक के तौर पर रेंडर होता है. parseIncompleteMarkdown से, खुले हुए **, __, `, ~~ बंद हो जाते हैं. साथ ही, [ लिंक की शुरुआत में मौजूद गैर-ज़रूरी जानकारी को छोटा कर दिया जाता है, ताकि रेंडर किया गया आउटपुट हर इंक्रीमेंटल चंक पर साफ़ रहे.
"सोचा जा रहा है…" स्थिति
मैसेज भेजने और पहला टोकन पाने के बीच, status 'submitted' होता है. इस विंडो के दौरान, ऐप्लिकेशन में ऐनिमेटेड शिमर दिखता है:
{status === 'submitted' && messages.at(-1)?.role !== 'assistant' && (
<ThinkingMessage />
)}
messages.at(-1)?.role !== 'assistant' शर्त की वजह से, Assistant के मैसेज की स्ट्रीमिंग शुरू होने के बाद, शाइनिंग इफ़ेक्ट दोबारा नहीं दिखता.
ThinkingMessage में Shimmer कॉम्पोनेंट का इस्तेमाल किया गया है: <span> में background-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 की वैल्यू false होने पर, 'सबसे नीचे स्क्रोल करें' बटन दिखता है. जब उपयोगकर्ता सबसे नीचे पहुंच जाता है, तो यह बटन गायब हो जाता है.
इनपुट एरिया
टेक्स्ट एरिया में टाइप करते समय, उसका साइज़ अपने-आप बदल जाता है. ऐसा इसलिए होता है, क्योंकि हर इनपुट इवेंट पर उसकी ऊंचाई 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 की पूरी रेंज देखी है. इसमें ब्राउज़र के बिल्ट-इन प्रॉम्प्ट एपीआई की मदद से, जनरेशन प्रिमिटिव से लेकर स्ट्रीम की गई चैट के बेहतर इंटरफ़ेस तक सब कुछ शामिल है.
Vercel AI SDK के साथ, पहले से मौजूद Prompt API का इस्तेमाल करना लेख में, आपने यह सीखा कि नॉन-स्ट्रीमिंग और स्ट्रीमिंग टेक्स्ट जनरेट करने के लिए, generateText() और streamText() का इस्तेमाल कैसे किया जाता है. साथ ही, आपने यह भी सीखा कि Output.object() की मदद से, स्ट्रक्चर्ड JSON आउटपुट का अनुरोध कैसे किया जाता है. इसके अलावा, आपने यह भी सीखा कि हाइब्रिड कोड कैसे लिखा जाता है, जो जनरेशन लॉजिक में कोई बदलाव किए बिना, रनटाइम के दौरान पहले से मौजूद मॉडल और क्लाउड सेवा देने वाली कंपनी में से किसी एक को चुनता है.
इस दस्तावेज़ में, आपने उन्हीं बिल्डिंग ब्लॉक का इस्तेमाल किया है और उन्हें पूरे React यूज़र इंटरफ़ेस (यूआई) में रैप किया है: बातचीत के लूप को मैनेज करने के लिए ToolLoopAgent, ब्राउज़र में सीधे तौर पर जवाब स्ट्रीम करने के लिए DirectChatTransport के साथ useChat, और Markdown जवाबों को साफ़ तौर पर रेंडर करने के लिए AI Elements कॉम्पोनेंट. ये सभी कॉम्पोनेंट, Prompt API के उपलब्ध न होने पर अपने-आप क्लाउड पर फ़ॉलबैक हो जाते हैं.
नतीजे के तौर पर, दो डेमो मिलते हैं. ये पूरी तरह से ब्राउज़र में काम करते हैं. इनके लिए, बैकएंड की ज़रूरत नहीं होती:
- Vercel AI SDK के डेमो के साथ, पहले से मौजूद Prompt API का इस्तेमाल करें: टेक्स्ट जनरेट करने, स्ट्रीमिंग करने, और स्ट्रक्चर्ड आउटपुट को एक साथ दिखाने की सुविधा
- Prompt API के डेमो के साथ Vercel AI SDK के यूज़र इंटरफ़ेस (यूआई) और एआई एलिमेंट का इस्तेमाल करना: इसमें मार्कडाउन रेंडरिंग और डार्क मोड की सुविधा वाला स्ट्रीमिंग चैटबॉट शामिल है