Arcana

О проекте
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 Недели
Заинтересованы в похожем проекте?
Давайте обсудим, как мы можем помочь воплотить ваше видение в жизнь.
Связаться с нами