Vercel AI SDK UI und AI Elements mit der Prompt API verwenden

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 als UIMessage[], jeweils mit einer role und einem parts-Array
  • sendMessage({ text }): sendet eine neue Nutzerrunde und beginnt mit dem Streaming der Antwort
  • Status: '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.

Eine Chat-Oberfläche mit einer Unterhaltung mit einem KI-Assistenten, einem Texteingabefeld und einem Bereich für Streaming-Antworten.

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: