За последний год мы наблюдаем бум ИИ‑помощников, и это не обошло стороной интерфейсы в Yandex Cloud: то в техподдержке завёлся чат‑бот с моделью, то в консоли — агент для рабочих операций. Команды подключали модели, продумывали диалоговую логику, рисовали дизайн и собирали чаты — и делали всё это поодиночке.

Разные команды собирали интерфейсы на общем фреймворке Gravity UI, но постепенно там появилось столько вариаций, что стало сложно поддерживать единый пользовательский опыт. Да и коллеги всё чаще сталкивались с тем, что тратят время на одни и те же решения. 

Чтобы перестать каждый раз изобретать велосипед, мы собрали накопленные практики в единый подход и сделали инструмент для чат‑ботов с ИИ — @gravity‑ui/aikit. Он позволяет создать полноценный интерфейс ассистента за несколько дней и при этом легко адаптировать его под разные сценарии.

Меня зовут Илья Ломтев, я старший разработчик в команде Foundation Services Yandex Cloud, и в статье я расскажу, почему мы решили собрать AIKit, как он устроен, немного о планах на будущее — и о том, что можно попробовать у себя.

Как и почему мы сделали AIKit

За последний год в Yandex Cloud выросло число сервисов с ИИ‑ассистентами, например: 

  • Code Assistant Chat в SourceCraft — ассистент помогает разработчикам писать код, а в режиме ИИ‑агента создаёт и настраивает репозитории, запускает CI/CD‑процессы, отвечает на вопросы по документации и автоматизирует задачи. Также умеет управлять issues, пул‑реквестами, работать с кодом: объяснять, создавать и редактировать файлы.

  • ИИ‑ассистент в облачной консоли — ассистент, разработанный для управления ресурсами в Yandex Cloud. Основная задача — помочь быстро и безопасно настраивать, изменять и управлять облачной инфраструктурой, скрывая сложность взаимодействия с API и инструментами.

В экосистеме возник десяток чатов, каждый со своей логикой, своим форматом сообщений и набором корнер‑кейсов. 

Мы обнаружили, что команды приходят к примерно одинаковому набору задач. Что нужно большинству: 

  • аккуратно отображать сообщения пользователя и ассистента, 

  • правильно организовать стриминг ответов, 

  • показывать индикатор «ассистент печатает», 

  • обрабатывать ошибки вроде оборвавшегося соединения или ретраев. 

Задачи по сути одинаковые, а вот путей решения много, и UX отличается. Например, расположение и способ отображения истории чатов: это может быть как отдельный экран, открывающийся как меню, так и список чатов в popup.

Проявилась проблема: опыт в разных чатах существенно отличался. Где‑то ассистент стримил ответ, а где‑то показывал сразу готовый текст. В одном интерфейсе сообщения группировались, а в другом — шли сплошной лентой. Это ломало общий UX — получалось, что пользователь переходит между продуктами одной экосистемы, а ощущения от ассистента совершенно разные. 

Примеры чатов, построенных на AIKit, в светлой теме
Примеры чатов, построенных на AIKit, в светлой теме

Ещё стало заметно, что раскатывать новые фичи в модели становилось всё сложнее. Чтобы донести до пользователей, например, инструментальность, мультимодальность или структурированные ответы тулов, нужно было согласовать контракт, доработать бэкенды, а затем обновить UI в каждой команде отдельно. В таких условиях любые изменения занимали много времени и плохо масштабировались.

Мы захотели остановить этот рост вариативности и вернуть предсказуемость. Для этого требовалось унифицировать модель данных и паттерны работы, дать готовые компоненты и хуки, чтобы командам не приходилось начинать с нуля, и оставить пространство для кастомизации — ведь сценарии у всех разные. 

Так мы пришли к идее отдельной библиотеки @gravity‑ui/aikit — это расширение Gravity UI, которое следует тем же принципам, но ориентировано на современные ИИ‑сценарии: диалоги, ассистентов, мультимодальность.

Архитектура AIKit: на что мы опирались

Проектируя AIKkit, мы ориентировались на опыт AI SDK и несколько фундаментальных принципов.

Atomic Design в основе: вся библиотека строится от атомов к страницам. Такая структура даёт чёткую иерархию, позволяет переиспользовать компоненты и при необходимости менять поведение на любом уровне. 

Полностью SDK‑agnostic: AIKit не зависит от конкретного ИИ‑провайдера. Можно использовать OpenAI, Alice AI LLM или свой бэкенд — UI принимает данные через props, а состояние и запросы остаются на стороне продукта. 

Два уровня использования для сложных сценариев: есть готовый компонент, который работает «из коробки», и есть хук с логикой, который позволяет полностью контролировать UI. Например, можно воспользоваться PromptInput или собрать своё поле ввода на базе usePromptInput. Это даёт гибкость без необходимости переписывать фундамент.

Расширяемая система типов. Чтобы обеспечить единообразие и типобезопасность, мы собрали расширяемую модель данных. Сообщения представлены единой типизированной структурой: есть сообщения пользователя, сообщения ассистента и несколько базовых типов контента — текст (text), размышления модели (thinking), инструменты (tool). При этом можно добавлять свои типы через MessageRendererRegistry

Всё это типизировано в TypeScript, что помогает быстрее собирать сложные сценарии и избежать ошибок на этапе разработки.

// 1. Определяем тип данных
type ChartMessageContent = TMessageContent<
    'chart',
    {
        chartData: number[];
        chartType: 'bar' | 'line';
    }
>;
// 2. Создаём компонент отображения
const ChartRenderer = ({part}: MessageContentComponentProps<ChartMessageContent>) => {
    return <div>Визуализация графика: {part.data.chartType}</div>;
};
// 3. Регистрируем рендерер
const customRegistry = registerMessageRenderer(createMessageRendererRegistry(), 'chart', {
    component: ChartRenderer,
});
// 4. Используем в AssistantMessage
<AssistantMessage message={message} messageRendererRegistry={customRegistry} />;

Наконец мы предусмотрели темизацию через CSS-переменные, добавили i18n (RU/EN), обеспечили доступность (ARIA, клавиатурная навигация), и настроили визуальные регрессионные тесты через Playwright Component Testing в Docker — и библиотека была готова к продакшн-использованию.

Что под капотом 

В основе AIKit — единая модель диалога. Чтобы её создать, для начала потребовалось разобраться с иерархией сообщений. 

Сообщения сами по себе довольно многогранные сущности. Есть первое сообщение от LLM — это один стрим. Но в рамках него может быть много разных вложенных сообщений: по сути это рассуждения, предложения, вызовы тулов для решения одного вопроса. Все эти разные подсообщения — по факту одно сообщение от бэкенда. Но также каждое из них вполне может быть отдельным сообщением в простом использовании LLM. 

Поэтому мы оставили возможность использовать чат обоими способами: сообщения могут быть вложены друг в друга, а могут быть плоскими — здесь всё зависит от потребности.

Управление состоянием при этом остаётся у сервиса. AIKit не хранит данные сам — он принимает их извне. Команды могут использовать React State, Redux, Zustand, Reatom — всё, что удобно. Мы лишь даём хуки, которые инкапсулируют типовую UI-логику, например: 

  • умную прокрутку с помощью useSmartScroll;

  • работу с датами, к примеру, форматирование дат с учётом локали useDateFormatter

  • обработку тул-сообщений useToolMessage;

  • и всё остальное, что нужно для построения диалога.

Вдобавок к этому AIKit остаётся расширяемым. Можно подключать любые модели, создавать собственные типы контента и строить UI полностью под свои задачи — пользуясь логикой из хуков или используя готовые компоненты как базу. Архитектура позволяет экспериментировать, не нарушая общих принципов.

Как собрать свой чат

Для создания своего первого чата воспользуемся подготовленным компонентом ChatContainer:

import React, { useState } from 'react';
import { ChatContainer } from 'aikit';
import type { ChatType, MessageType } from 'aikit';

function App() {
    const [messages, setMessages] = useState<MessageType[]>([]);
    const [chats, setChats] = useState<ChatType[]>([]);
    const [activeChat, setActiveChat] = useState<ChatType | null>(null);

    const handleSendMessage = async (content: string) => {
        // Your message sending logic
        const response = await fetch('/api/chat', {
            method: 'POST',
            body: JSON.stringify({ message: content })
        });
        const data = await response.json();

        // Update state
        setMessages(prev => [...prev, data]);
    };

    return (
  <ChatContainer
    messages={[]}
    onSendMessage={() => {}}
    welcomeConfig={{
      description: 'Start a conversation by typing a message or selecting a suggestion.',
      image: <Icon data={() => {}} size={48}/>,
      suggestionTitle: 'Try asking:',
      suggestions: [
        {
          id: '1',
          title: 'Explain quantum computing in simple terms'
        },
        {
          id: '2',
          title: 'Write a poem about nature'
        },
        {
          id: '3',
          title: 'Help me debug my JavaScript code'
        },
        {
          id: '4',
          title: 'Summarize recent AI developments'
        }
      ],
      title: 'Welcome to AI Chat'
    }}
/>
        
    );
}

«Из коробки» всё выглядит вот так:

Добавим немного праздника: 

  1. Поправим начальное состояние. 

    Для более тонкой настройки соберём чат из отдельных компонентов: Header, MessageList, PromptBox.

    import { Header, MessageList, PromptBox } from 'aikit';
    function CustomChat() {
        return (
            <div className="custom-chat">
                <Header title="AI Assistant" onNewChat={() => {}} />
                <MessageList messages={messages} showTimestamp />
                <PromptBox onSend={handleSend} placeholder="Спросите что угодно..." />
            </div>
        );
    }
  2. Применим разные встроенные типы сообщений, импортированные через MessageType.

    thinking — покажет процесс размышления ИИ (так пользователь может изучить логику, по которой ассистент готовит ответ).

    tool — подойдёт для отображения интерактивных блоков ответа, в нашем случае, это блок с кодом, в котором корректно работает подсветка синтаксиса, поддержаны операции редактирования и копирования в буфер обмена.

    Также можно добавлять собственные типы, например, сообщения с изображениями:

    type ImageMessage = BaseMessage<ImageMessageData> & { type: 'image' };
    
    
    const ImageMessageView = ({ message }: { message: ImageMessage }) => (
        <div>
            <img src={message.data.imageUrl} />
            {message.data.caption && <p>{message.data.caption}</p>}
        </div>
    );
    
    
    const customTypes: MessageTypeRegistry = {
        image: {
            component: ImageMessageView,
            validator: (msg) => msg.type === 'image'
        }
    };
    
    
    <ChatContainer messages={messages} messageTypeRegistry={customTypes} />
  3. Добавим стилизацию через CSS…

…и получим чат с Дедом Морозом:)

Для полной кастомизации отдельных элементов можно использовать хуки — будем рады увидеть ваши варианты стилизации в комментариях под статьёй!

Как AIKit повлиял на сервисы

Результат использования AIKit в Yandex Cloud стал заметен быстро. Во всех сервисах ассистенты стали вести себя одинаково: одинаково стримить ответы, одинаково показывать ошибки, одинаково группировать сообщения. UX стал единообразным, теперь с ним проще взаимодействовать во всей экосистеме, поведение более ожидаемое и предсказуемое.

  • UX-язык стал единым — чаты ассистентов в разных продуктах теперь ощущаются как часть одной экосистемы. Пользователи видят предсказуемое поведение: одинаковый стриминг, обработку ошибок, паттерны взаимодействия. 

  • Скорость разработки UI чата гораздо выше. 

  • Централизованное развитие — новые фичи вроде типа контента thinking или улучшенной работы с тулами добавляются один раз и автоматически доступны всем. 

  • Библиотека стала основой для формирования стандартов ИИ-интерфейсов в экосистеме.

Что дальше

Теперь о планах. Мы выделили несколько направлений: 

  1. Улучшение производительности через виртуализацию для работы с очень большими историями чатов. 

  2. Расширение базовых сценариев под новые возможности ИИ‑агентов, которые активно развиваются. 

  3. Добавление утилит, чтобы упростить маппинг данных популярных ИИ‑моделей в нашу модель данных чата. 

Дополнительно будем развивать документацию и примеры. И, конечно, развитие сообщества — хотим, чтобы библиотека была полезна не только внутри компании, но и внешним разработчикам.

Как попробовать AIKit 

Переходите в раздел библиотеки на нашем сайте. Если вы делаете собственный ИИ‑ассистент, хотите быстрый и предсказуемый чат‑интерфейс и уже используете Gravity UI (или готовы попробовать), загляните в README и примеры. И ещё будем благодарны за обратную связь — заводите issue, присылайте PR, рассказывайте, что ещё нужно для ваших сценариев!

Если Вам нравится наш проект, будем рады ⭐️ в AIKit и UIKit!

Комментарии (1)


  1. antirek
    19.12.2025 06:18

    круто, мы делаем библиотеку для чатов на vuejs https://github.com/mobilon-dev/chotto - это не совсем пока про AI, но близко. спасибо за изложение Atomic Design