Arcana

Arcana
FlutterDart

О проекте

Arcana — ИИ-компаньон для чтения карт таро: пользователи общаются с ботом через чат-интерфейс и получают персональные расклады. Бэкенд с логикой ИИ и знаниями о таро разрабатывала команда клиента. Наша зона ответственности — Flutter-клиент: производительный, отшлифованный чат-интерфейс для iOS и Android.

Сама природа продукта сразу обозначила техническую проблему. Сессии растут в длину. Пользователи возвращаются ежедневно, история накапливается и может насчитывать тысячи сообщений. При этом ИИ стримит ответы в реальном времени — выводит форматированный текст с жирными выделениями, курсивом, заголовками и маркированными списками. Выстроить чат-экран, который корректно справляется с обоими сценариями при 60 fps на бюджетном железе, — вот в чём состояла суть нашей работы.

Карта дня

Карта дня

Стриминг ответа в чате

Стриминг ответа в чате

Расклад карт

Расклад карт

Стриминг в реальном времени через SSE

Ответы ИИ поступают потоком токенов, а не единым пакетом. Мы реализовали SSE-клиент на Flutter, который открывает постоянное HTTP-соединение с бэкендом и обрабатывает поток токенов по мере их поступления. Каждый входящий фрагмент добавляется к активному сообщению в чате, создавая привычный эффект «печатания» — пользователь видит, как интерпретация появляется слово за словом.

Работа с SSE во Flutter потребовала аккуратного управления жизненным циклом соединения: переподключение при обрывах сети без потери частично полученного контента, буферизация неполных UTF-8 последовательностей на границах чанков, а также обновление только виджета активного сообщения, а не всего списка, при каждом новом токене.

Рендеринг Markdown на лету

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

Мы разработали собственный инкрементальный рендерер Markdown, который обрабатывает растущую строку при каждом обновлении, не перепарсивая всё сообщение с начала. Незакрытые токены в конце текущего буфера (незакрытый ** или недописанный заголовок) удерживаются в состоянии ожидания и отображаются как обычный текст до разрешения разметки. Это исключает мерцание и скачки макета, сохраняя корректное форматирование по мере завершения ответа.

Бенчмарки производительности на бюджетных устройствах

Длинные истории чатов — норма для приложения с ежедневным использованием. Мы проектировали систему под 5 000+ сообщений без деградации и подтвердили это структурированными бенчмарками на бюджетных Android-устройствах — телефонах, представляющих нижний ценовой сегмент, где производительность Flutter испытывает наибольшую нагрузку.

Наш набор бенчмарков прокручивал списки сообщений разного объёма (500, 2 000 и 5 000 сообщений) с одновременным активным SSE-стримингом, фиксируя время кадра на протяжении всего теста. В начальных сборках при быстром скролле с отметки 2 000+ сообщений наблюдались просадки кадров: проходы лейаута для бабблов с Markdown-рендерингом становились дорогостоящими.

Мы устранили это несколькими точечными оптимизациями:

Ленивое кэширование лейаутов: Отрендеренные Markdown-лейауты кэшируются по ID сообщения. Как только баббл был размечен, его размеры и дерево виджетов переиспользуются при последующих прокрутках вместо повторного вычисления.

Виртуализация через Sliver: Список чата построен на SliverList с кастомным делегатом, который не строит невидимые виджеты. Инстанцируются только сообщения в пределах или вблизи видимого вьюпорта — дерево виджетов остаётся неглубоким независимо от общего числа сообщений.

Изолированные ребилды стрима: Стримящееся сообщение в конце списка управляется отдельно от исторического. Обновления SSE-токенов инициируют целевой ребилд только виджета активного баббла, не затрагивая виртуализированный список.

После этих оптимизаций время кадра стабильно удерживалось ниже 16 мс во всех сценариях бенчмарка — включая историю из 5 000 сообщений с активным стримингом — на целевых бюджетных устройствах.

Результаты

Итоговый клиент обеспечивает плавный и отзывчивый чат при любой глубине истории и на любом устройстве целевого диапазона. Стримящиеся ответы отображаются гладко в реальном времени с корректным Markdown-форматированием на всём протяжении, а долгосрочные пользователи с сотнями сессий не сталкиваются с замедлениями. Заложенный фундамент производительности даёт продукту возможность расти без необходимости пересматривать архитектуру рендеринга.

Детали проекта

Дата
10 февраля 2026 г.
Продолжительность
3 Недели

Заинтересованы в похожем проекте?

Давайте обсудим, как мы можем помочь воплотить ваше видение в жизнь.

Связаться с нами