Используйте пользовательский интерфейс и элементы искусственного интеллекта из Vercel AI SDK с API Prompt.

Опубликовано: 16 июля 2026 г.

В разделе «Использование встроенного API Prompt с SDK Vercel AI» вы увидели четыре основных примитива генерации, а именно generateText() , streamText() , гибридный код и структурированный вывод с помощью Output.object() , все они основаны на аннотации @browser-ai/core . На этот раз вы создадите нечто более интерактивное: полноценный потоковый чат, работающий полностью в браузере, с автоматическим переключением на облачную модель, если API Prompt недоступен.

То, что вы строите

Чат-интерфейс на React, выполняющий следующие функции:

  • Использует хук useChat из Vercel AI SDK для потоковой передачи многоходового диалога.
  • Запускает цикл моделирования в браузере без необходимости использования серверной части.
  • При недоступности API Prompt автоматически переключается на версию прошивки Gemini 2.5.
  • Визуализирует ответы помощника в формате Markdown, аналогичном GitHub, обрабатывая неполные токены во время потоковой передачи.
  • Во время ожидания первого жетона появляется мерцание, напоминающее надпись "Размышляю…".
  • Автоматическая прокрутка до новых сообщений, с кнопкой прокрутки вниз после прокрутки вверх.

Дополнительные зависимости

Помимо ai , @browser-ai/core и @ai-sdk/google , для пользовательского интерфейса чата необходимы React, привязки React из AI SDK и несколько пакетов 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"> и тег <script>:

<!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>

Автоматический выбор модели

В чат-боте приложение делает этот выбор автоматически: сначала оно пробует встроенную модель, а если API Prompt недоступен, переключается на облако.

Это происходит во время загрузки модуля, до монтирования 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 ; это абстракция SDK для ИИ, которая управляет многоэтапным циклом диалога поверх любой модели. Она принимает модель и системный запрос и обрабатывает обмен данными внутри себя.

Подключите агента к использованию чата.

Хук useChat из пакета @ai-sdk/react обычно взаимодействует с HTTP-терминалом. Для вывода информации на стороне браузера используйте 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' . Сообщения пользователей отображаются в виде всплывающих окон, выровненных по правому краю; сообщения ассистента выравниваются по левому краю и имеют значок:

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 это элементы AI . Это исходные компоненты в стиле shadcn , которые вы копируете в свой проект, а не устанавливаете из npm. Response использует react-markdown с remark-gfm для Markdown в стиле GitHub (таблицы, списки задач, зачеркивание) и harden-react-markdown для очистки ссылок и изображений в выходных данных AI.

Свойство parseIncompleteMarkdown имеет true , пока сообщение передается потоком. Во время передачи модель может записывать **bold , но еще не закрывать ** , оставляя висячий токен, который будет отображаться как обычные звездочки. parseIncompleteMarkdown закрывает все открытые ** , __ , ` , ~~ и обрезает висячие [ начальные точки ссылок, чтобы отображаемый вывод оставался чистым на каждом последующем фрагменте.

Состояние "Мышление…"

В промежутке между отправкой сообщения и получением первого токена status'submitted' . В течение этого времени приложение отображает анимированное мерцание:

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

Условие messages.at(-1)?.role !== 'assistant' предотвращает повторное появление мерцания после начала потока сообщений от помощника.

Компонент ThinkingMessage использует элемент Shimmer : <span> с движущимся градиентом, использующий background-clip: text , который придает тексту "Thinking…" эффект плавного выделения.

Автоматическая прокрутка

Когда появляется новый контент, приложение прокручивается до конца, но только если пользователь уже находится внизу. Прокручивать страницу, отвлекая пользователя от чтения посреди разговора, было бы неудобно.

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 . Если доступен API Prompt, ответ будет отправлен непосредственно с устройства без сетевых запросов. Если ваш браузер не поддерживает API Prompt, он автоматически переключится на Gemini 2.5 Flash. Попробуйте попросить его объяснить что-либо в списке, написать фрагмент кода или использовать форматирование Markdown. Ответы отображаются с отформатированными блоками кода, таблицами и встроенным кодом сразу после установки.

Интерфейс чата, отображающий разговор с ИИ-помощником, включающий поле для ввода текста и область для потокового ответа.

Заключение

В этих двух статьях вы увидели весь спектр возможностей, которые предоставляет SDK Vercel AI с помощью встроенного в браузер API подсказок, от простых примитивов генерации до отточенного интерфейса потокового чата.

В разделе «Использование встроенного API Prompt с SDK Vercel AI» вы узнали, как использовать generateText() и streamText() для генерации текста без потоковой передачи и с потоковой передачей, как запрашивать структурированный вывод JSON с помощью Output.object() , а также как писать гибридный код, который выбирает между встроенной моделью и облачным провайдером во время выполнения без изменения логики генерации.

В этом документе вы взяли те же самые базовые элементы и обернули их в полноценный пользовательский интерфейс React: ToolLoopAgent для управления циклом диалога, useChat с DirectChatTransport для потоковой передачи ответов непосредственно в браузере и компоненты AI Elements для корректного отображения ответов в формате Markdown по мере их поступления; и все это с автоматическим резервным копированием в облако, если API Prompt недоступен.

В результате получились две демонстрационные версии, которые полностью работают в браузере и не требуют наличия бэкэнда: