Veröffentlicht am 16. Juli 2026
Im Artikel Die integrierte Prompt API mit dem Vercel AI
SDK verwenden haben Sie die vier wichtigsten Generierungs
primitive kennengelernt: generateText(), streamText(), Hybridcode und strukturierte Ausgabe mit
Output.object(). Alle werden von @browser-ai/core unterstützt. Dieses Mal erstellen Sie etwas Interaktiveres: eine vollständige Streaming-Chat-UI, die vollständig im Browser ausgeführt wird, mit automatischem Fallback auf ein Cloud-Modell, wenn die Prompt API nicht verfügbar ist.
Was Sie erstellen
Eine React-Chatoberfläche, die Folgendes bietet:
- Verwendet den
useChat-Hook des Vercel AI SDK für Streaming-Unterhaltungen mit mehreren Runden. - Führt die Modellschleife im Browser aus, ohne dass ein Backend-Server erforderlich ist.
- Wechselt automatisch zu Gemini 2.5 Flash, wenn die Prompt API nicht verfügbar ist.
- Rendert Antworten des Assistenten als GitHub-Markdown und verarbeitet unvollständige Tokens während des Streamings.
- Zeigt ein „Denkt nach…“-Shimmer an, während auf das erste Token gewartet wird.
- Scrollt automatisch zu neuen Nachrichten. Wenn Sie nach oben gescrollt haben, wird eine Schaltfläche zum Scrollen nach unten angezeigt.
Weitere Abhängigkeiten
Neben ai, @browser-ai/core und @ai-sdk/google benötigt die Chat-UI
React, die React-Bindungen des AI SDK und einige Markdown-Pakete:
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
Fügen Sie für die UI außerdem Tailwind CSS, einige Komponentenhilfsprogramme und Lucide für Symbole hinzu:
npm install -D tailwindcss postcss autoprefixer
npm install clsx tailwind-merge lucide-react
Abstimmungskonfiguration: Build mit mehreren Einträgen
Das Projekt enthält bereits index.html. Fügen Sie chat.html als zweiten Einstiegspunkt hinzu und konfigurieren Sie Vite so, dass beide erstellt werden:
// 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 ist minimal und besteht nur aus einem <div id="root"> und einem Skript-Tag:
<!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>
Automatische Modellauswahl
Im Chatbot trifft die App diese Entscheidung automatisch: Sie versucht zuerst das integrierte Modell und wechselt zur Cloud, wenn die Prompt API nicht verfügbar ist.
Dies geschieht bei der Ladezeit des Moduls, bevor React eingebunden wird, sodass der Agent bereit ist, wenn der Nutzer seine erste Nachricht eingibt:
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.' });
})();
Das neue Element ist ToolLoopAgent, die AI SDK-Abstraktion, die eine Unterhaltungsschleife mit mehreren Runden für jedes Modell verwaltet. Es verwendet das Modell und einen System-Prompt und verarbeitet die Hin- und Her-Kommunikation intern.
Agent mit useChat verbinden
Der useChat-Hook des Pakets @ai-sdk/react kommuniziert normalerweise mit einem HTTP-Endpunkt.
Verwenden Sie stattdessen DirectChatTransport für die Inferenz auf der Browserseite. Dadurch wird die ToolLoopAgent-Schleife vollständig im Browser ausgeführt, ohne dass ein Server erforderlich ist:
const transport = useMemo(() => new DirectChatTransport({ agent }), [agent]);
const { messages, sendMessage, status, stop } = useChat({ transport });
useMemo ist wichtig, da DirectChatTransport den Unterhaltungsstatus enthält und daher eine stabile Referenz sein muss. Wenn es bei jedem Rendering neu erstellt wird, wird die Unterhaltung zurückgesetzt.
useChat bietet Folgendes:
Messages: die vollständige Unterhaltung alsUIMessage[], jeweils mit einerroleund einemparts-ArraysendMessage({ text }): sendet eine neue Nutzerrunde und beginnt mit dem Streaming der AntwortStatus:'idle'|'submitted'|'streaming'|'error'Stop: bricht eine laufende Generierung ab
Nachrichten rendern
Jede Nachricht hat ein parts-Array. Für diesen Chatbot sind nur type:
'text'-Teile relevant. Nutzernachrichten werden als rechts ausgerichtete Sprechblase angezeigt, Assistentennachrichten links mit einem Symbol:
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 und Response sind AI-Elemente.
Es handelt sich um Shadcn-Quellkomponenten im Stil, die Sie in
Ihr Projekt kopieren, anstatt sie über npm zu installieren. Response umschließt react-markdown
mit remark-gfm für GitHub-Markdown (Tabellen, Aufgabenlisten,
Durchstreichen) und harden-react-markdown, um Links und Bilder in der KI
Ausgabe zu bereinigen.
Die parseIncompleteMarkdown-Property ist true, während die Nachricht noch gestreamt wird. Während des Streamings kann das Modell **bold schreiben, aber
** noch nicht schließen. Dadurch entsteht ein hängendes Token, das als wörtliche
Sternchen gerendert würde. parseIncompleteMarkdown schließt alle offenen **, __, `, ~~ und
kürzt hängende [-Linkstarts, damit die gerenderte Ausgabe in jedem
inkrementellen Chunk sauber bleibt.
Der Status „Denkt nach…“
Zwischen dem Senden einer Nachricht und dem Empfang des ersten Tokens ist status gleich
'submitted'. Während dieses Zeitfensters zeigt die App einen animierten Shimmer an:
{status === 'submitted' && messages.at(-1)?.role !== 'assistant' && (
<ThinkingMessage />
)}
Die Bedingung messages.at(-1)?.role !== 'assistant' verhindert, dass der Shimmer wieder angezeigt wird, sobald das Streaming der Assistentennachricht begonnen hat.
ThinkingMessage verwendet eine Shimmer-Komponente: ein <span> mit einem bewegten Farbverlauf
der mit background-clip: text den Text „Denkt nach…“ hervorhebt.
Automatisches Scrollen
Wenn neue Inhalte eingehen, scrollt die App nach unten, aber nur, wenn der Nutzer sich bereits unten befindet. Es wäre störend, wenn der Nutzer beim Lesen einer Nachricht in der Mitte der Unterhaltung nach unten gescrollt würde.
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);
};
Eine schwebende Schaltfläche zum Scrollen nach unten wird angezeigt, wenn isAtBottom falsch ist, und wird ausgeblendet, wenn der Nutzer wieder unten ist.
Der Eingabebereich
Die Textarea wird automatisch angepasst, während Sie tippen. Dazu wird die Höhe bei jedem Eingabeereignis auf auto zurückgesetzt und dann auf scrollHeight gesetzt. Die Eingabe wird mit der Eingabetaste gesendet (nicht mit Umschalt + Eingabetaste). Während eine Antwort gestreamt wird, wird die Schaltfläche „Senden“ durch eine Schaltfläche „Beenden“ ersetzt, die stop() aufruft:
<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>
);
}
Mit einem Ladestatus einbinden
Da agentPromise asynchron ist, warten Sie darauf, bevor Sie Chat rendern. Ein App-Wrapper löst das Promise auf und zeigt in der Zwischenzeit einen Spinner an:
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} />;
}
Sobald der Agent aufgelöst wird, unabhängig davon, ob das integrierte Modell sofort gestartet wird oder auf einen Modelldownload gewartet wird, verschwindet der Spinner und die Chat-UI wird eingebunden.
Demo
Die Live-Demo ist ein voll funktionsfähiger Chatbot, der vollständig in Ihrem Browser ausgeführt wird. Geben Sie eine Nachricht ein und drücken Sie die Eingabetaste. Wenn die Prompt API verfügbar ist, wird die Antwort direkt vom On-Device-Modell gestreamt, ohne dass eine Netzwerkanfrage erforderlich ist. Wenn Ihr Browser die Prompt API nicht unterstützt, wird automatisch auf Gemini 2.5 Flash zurückgegriffen. Sie können den Chatbot beispielsweise bitten, etwas in einer Liste zu erklären, einen Code-Snippet zu schreiben oder die Markdown-Formatierung zu verwenden. Die Antworten werden sofort mit formatierten Codeblöcken, Tabellen und Inline-Code gerendert.

Fazit
In diesen beiden Artikeln haben Sie das gesamte Spektrum der Möglichkeiten des Vercel AI SDK mit der integrierten Prompt API des Browsers kennengelernt, von einfachen Generierungsprimitiven bis hin zu einer ausgereiften Streaming-Chatoberfläche.
Im Artikel Die integrierte Prompt API mit dem Vercel AI SDK verwenden haben Sie erfahren, wie Sie
generateText() und streamText() für die Textgenerierung ohne und mit Streaming
verwenden, wie Sie mit Output.object() strukturierte JSON-Ausgabe anfordern und
wie Sie Hybridcode schreiben, der zur Laufzeit zwischen dem integrierten Modell und einem Cloud
Anbieter auswählt, ohne dass Änderungen an der Generierungslogik erforderlich sind.
In diesem Dokument haben Sie dieselben Bausteine verwendet und sie in eine vollständige React-UI eingebunden: einen ToolLoopAgent zur Verwaltung der Unterhaltungsschleife, useChat mit DirectChatTransport zum Streamen von Antworten direkt im Browser und AI-Elemente-Komponenten zum sauberen Rendern von Markdown-Antworten, sobald sie eingehen. Außerdem wird automatisch auf die Cloud zurückgegriffen, wenn die Prompt API nicht verfügbar ist.
Das Ergebnis sind zwei Demos, die vollständig im Browser funktionieren, ohne dass ein Backend erforderlich ist:
- Demo: Die integrierte Prompt API mit dem Vercel AI SDK verwenden: Textgenerierung, Streaming und strukturierte Ausgabe nebeneinander
- Demo: Vercel AI SDK-UI und AI-Elemente mit der Prompt API verwenden: ein vollständiger Streaming-Chatbot mit Markdown-Rendering und Dark Mode