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

Один чат, чтобы править всеми: собрали библиотеку для ИИ-ассистентов на базе Gravity UI

ebb6b6e0610bfd0e98e8cd11d734a506.png

За последний год мы наблюдаем бум ИИ‑помощников, и это не обошло стороной интерфейсы в 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 в основе: вся библиотека строится от атомов к страницам. Такая структура даёт чёткую иерархию, позволяет переиспользовать компоненты и при необходимости менять поведение на любом уровне.

fe1faaca77c0501459a396977dd71e3c.png

Полностью 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' }} /> ); }

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

3db5f6ac7b2fe1d182c095c0e96eb04c.png

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

  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…

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

cfa21cd515e5dcf2ed21ecafb098ae0f.png

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

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

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

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

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

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

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

Что дальше

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

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

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

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

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

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

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

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

Источник

Возможности рынка
Логотип Cloud
Cloud Курс (CLOUD)
$0.08036
$0.08036$0.08036
-2.48%
USD
График цены Cloud (CLOUD) в реальном времени
Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу service@support.mexc.com для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.