Dipublikasikan: 16 Juli 2026
Di artikel Menggunakan Prompt API bawaan dengan Vercel AI
SDK, Anda telah melihat empat primitif pembuatan
inti, yaitu generateText(), streamText(), kode hybrid, dan output terstruktur menggunakan
Output.object(), yang semuanya didukung oleh @browser-ai/core. Kali ini, Anda akan membuat sesuatu yang lebih interaktif: UI chat streaming lengkap yang berjalan sepenuhnya di browser, dengan otomatis beralih ke model cloud jika Prompt API tidak tersedia.
Yang akan Anda buat
Antarmuka chat React yang melakukan hal berikut:
- Menggunakan hook
useChatVercel AI SDK untuk percakapan multi-turn streaming. - Menjalankan loop model di browser tanpa memerlukan server backend.
- Otomatis beralih ke Gemini 2.5 Flash jika Prompt API tidak tersedia.
- Merender balasan asisten sebagai Markdown yang disesuaikan dengan GitHub, menangani token yang tidak lengkap selama streaming.
- Menampilkan shimmer "Thinking…" saat menunggu token pertama.
- Men-scroll otomatis ke pesan baru, dengan tombol scroll ke bawah saat Anda men-scroll ke atas.
Dependensi tambahan
Selain ai, @browser-ai/core, dan @ai-sdk/google, UI chat memerlukan
React, binding React AI SDK, dan beberapa paket 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
Untuk UI, tambahkan juga Tailwind CSS, beberapa utilitas komponen, dan Lucide untuk ikon:
npm install -D tailwindcss postcss autoprefixer
npm install clsx tailwind-merge lucide-react
Konfigurasi Vite: build multi-entri
Project ini sudah memiliki index.html. Tambahkan chat.html sebagai titik entri kedua dan konfigurasi Vite untuk membuat keduanya:
// 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 bersifat minimal, hanya <div id="root"> dan tag skrip:
<!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>
Pemilihan model otomatis
Di chatbot, aplikasi membuat pilihan tersebut secara otomatis: aplikasi mencoba model bawaan terlebih dahulu, dan beralih ke cloud jika Prompt API tidak tersedia.
Hal ini dilakukan pada waktu pemuatan modul, sebelum React dipasang, sehingga agen siap saat pengguna mengetik pesan pertamanya:
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.' });
})();
Bagian baru ini adalah ToolLoopAgent; abstraksi AI SDK yang mengelola loop percakapan multi-turn di atas model apa pun. Bagian ini menggunakan model dan perintah sistem, serta menangani bolak-balik secara internal.
Menghubungkan agen ke useChat
Hook useChat paket @ai-sdk/react biasanya berkomunikasi dengan endpoint HTTP.
Untuk inferensi sisi browser, gunakan DirectChatTransport. Bagian ini menjalankan loop ToolLoopAgent sepenuhnya di browser tanpa melibatkan server:
const transport = useMemo(() => new DirectChatTransport({ agent }), [agent]);
const { messages, sendMessage, status, stop } = useChat({ transport });
useMemo penting karena DirectChatTransport menyimpan status percakapan, sehingga harus menjadi referensi yang stabil. Membuat ulang pada setiap render akan mereset percakapan.
useChat memberi Anda:
Messages: percakapan lengkap sebagaiUIMessage[], masing-masing denganroledan arraypartssendMessage({ text }): mengirim giliran pengguna baru dan mulai melakukan streaming responsStatus:'idle'|'submitted'|'streaming'|'error'Stop: membatalkan pembuatan yang sedang berlangsung
Merender pesan
Setiap pesan memiliki array parts. Untuk chatbot ini, kita hanya memperhatikan bagian type:
'text'. Pesan pengguna muncul sebagai gelembung yang diratakan ke kanan; pesan asisten diratakan ke kiri dengan ikon:
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 dan Response adalah Elemen AI.
Keduanya adalah komponen sumber gaya shadcn yang Anda salin ke
project, bukan diinstal dari npm. Response menggabungkan react-markdown
dengan remark-gfm untuk Markdown yang disesuaikan dengan GitHub (tabel, daftar tugas,
coretan) dan harden-react-markdown untuk membersihkan link dan gambar dalam output AI.
Properti parseIncompleteMarkdown adalah true saat pesan masih di-streaming. Selama streaming, model dapat menulis **bold tetapi belum
menutup **, sehingga menyisakan token yang tidak lengkap yang akan dirender sebagai tanda bintang literal. parseIncompleteMarkdown menutup **, __, `, ~~ yang terbuka, dan
memangkas awal link [ yang tidak lengkap sehingga output yang dirender tetap bersih pada setiap
bagian tambahan.
Status "Thinking…"
Antara mengirim pesan dan menerima token pertama, status adalah
'submitted'. Selama periode ini, aplikasi menampilkan shimmer animasi:
{status === 'submitted' && messages.at(-1)?.role !== 'assistant' && (
<ThinkingMessage />
)}
Kondisi messages.at(-1)?.role !== 'assistant' mencegah shimmer
muncul kembali setelah pesan asisten mulai di-streaming.
ThinkingMessage menggunakan komponen Shimmer: <span> dengan gradien bergerak
menggunakan background-clip: text yang memberikan efek sorot
menyapu pada teks "Thinking…".
Scroll otomatis
Saat konten baru tiba, aplikasi akan men-scroll ke bagian bawah, tetapi hanya jika pengguna sudah berada di bagian bawah. Men-scroll mereka dari sesuatu yang sedang mereka baca di tengah percakapan akan mengganggu.
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);
};
Tombol scroll ke bawah mengambang akan muncul jika isAtBottom salah (false), dan memudar saat pengguna kembali ke bagian bawah.
Area input
Area teks akan otomatis diubah ukurannya saat Anda mengetik dengan mereset tingginya ke auto pada setiap peristiwa input, lalu menetapkannya ke scrollHeight. Area teks akan dikirim saat Enter ditekan (bukan Shift+Enter), dan saat respons di-streaming, tombol Kirim akan diganti dengan tombol Berhenti yang memanggil 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>
);
}
Memasang dengan status pemuatan
Karena agentPromise bersifat asinkron, tunggu hingga selesai sebelum merender Chat. Wrapper App akan menyelesaikan promise dan menampilkan pemintal sementara:
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} />;
}
Setelah agen menyelesaikan, baik itu model bawaan yang langsung di-booting atau menunggu download model, pemintal akan menghilang dan UI chat akan dipasang.
Demo
Demo langsung adalah chatbot yang berfungsi penuh dan berjalan sepenuhnya di browser Anda. Ketik pesan, lalu tekan Enter. Jika Prompt API tersedia, streaming balasan akan langsung berasal dari model di perangkat tanpa permintaan jaringan. Jika browser Anda tidak mendukung Prompt API, browser akan otomatis beralih ke Gemini 2.5 Flash. Coba minta chatbot untuk menjelaskan sesuatu dalam daftar, menulis cuplikan kode, atau menggunakan pemformatan Markdown. Respons akan dirender dengan blok kode, tabel, dan kode inline yang diformat.

Kesimpulan
Selama dua artikel ini, Anda telah melihat berbagai kemungkinan yang dapat dilakukan Vercel AI SDK dengan Prompt API bawaan browser, mulai dari primitif pembuatan mentah hingga antarmuka chat streaming yang disempurnakan.
Di artikel Menggunakan Prompt API bawaan dengan Vercel AI SDK, Anda telah mempelajari cara
menggunakan generateText() dan streamText() untuk pembuatan teks non-streaming dan streaming, cara meminta output JSON terstruktur dengan Output.object(), dan
cara menulis kode hybrid yang memilih antara model bawaan dan penyedia cloud
saat runtime tanpa mengubah logika pembuatan.
Dalam dokumen ini, Anda telah menggunakan blok penyusun yang sama dan menggabungkannya dalam UI React lengkap: ToolLoopAgent untuk mengelola loop percakapan, useChat dengan DirectChatTransport untuk melakukan streaming respons langsung di browser, dan komponen Elemen AI untuk merender respons Markdown secara bersih saat respons tiba; semuanya dengan otomatis beralih ke cloud jika Prompt API tidak tersedia.
Hasilnya adalah dua demo yang berfungsi sepenuhnya di browser, tanpa memerlukan backend:
- Demo Menggunakan Prompt API bawaan dengan Vercel AI SDK: pembuatan teks, streaming, dan output terstruktur secara berdampingan
- Demo Menggunakan UI Vercel AI SDK dan Elemen AI dengan Prompt API: chatbot streaming lengkap dengan rendering Markdown dan mode gelap