Над лентой висит несколько узких полос и баннеров, под лентой — поле ввода, а поверх неё может раскрыться панель плана. Лента не перерисовывает всё сразу: приложение держит в памяти только окно последних элементов, а старая история подгружается порциями.
Что висит над лентой
Полосы появляются независимо друг от друга и каждая включается по своим условиям:
- Полоса расхода контекста показывает, сколько токенов занято в окне контекста, какая доля входа пришлась на кэш, сколько процентов контекста заполнено и во сколько обошёлся последний ход. Сам процент заполнения красится по порогам: до 70 бирюзовый, от 70 жёлтый, от 90 красный. Полоса есть только когда в ленте есть хотя бы одна карточка и агент уже прислал данные о расходе токенов. Цифры не перескакивают скачком, а плавно докручиваются, а при смене треда анимация начинается заново, от новых значений.
- Полоса лимитов модели появляется, когда израсходовано не меньше 70 процентов доступного лимита: показывает процент, окрашенный по порогам (жёлтый до 90 процентов, красный дальше). От 80 процентов рядом появляется кнопка Switch, которая открывает окно выбора модели.
- Баннер связи появляется только в открытом треде, когда соединение с агентом оборвалось или завершилось ошибкой. В нём написано, что произошло, и есть кнопка Retry, которая пробует подключиться снова. Пока связь жива или идёт переподключение, баннера нет.
- Баннер залипания кэша появляется, когда сторож подозревает, что запросы идут почти без кэша и вы платите полную цену. В нём написан процент попадания в кэш и две кнопки: Reset сбрасывает сессию кэша, Continue скрывает предупреждение на час.
- Плашка «агент не отвечает» стоит не над лентой, а в самом низу ленты, прямо над полем ввода, и уезжает вместе с прокруткой. Плашка показывает, сколько минут агент молчит, и предлагает проверить его или прервать ход. Если ход потерян на сервере намертво, вместо этого написано, что новые сообщения ждут в очереди, пока вы не разблокируете тред.
- Строка загрузки страницы на время работы веб-поиска вытесняет полосу контекста: видно домен, размер ответа и время загрузки, а при ошибке — её причину. Через пять секунд после окончания загрузки строка исчезает.
Как устроена лента
- Карточки идут строго снизу вверх. Самое новое сообщение всегда внизу, а сверху — самая старая история. Порядок карточек совпадает с тем, в каком порядке агент выполнял шаги.
- Пустые заглушки в ленту не попадают. Карточка, в которой ещё нет текста, или шаг без содержимого просто не рисуется, чтобы между настоящими блоками не было лишних провалов.
- Соседние куски мыслей одного хода склеиваются в одну карточку. Агент присылает размышления порциями, и лента показывает их одним блоком с общей длительностью, а не десятком отдельных плашек.
- Соседние действия по изучению файлов тоже склеиваются. Чтение, поиск и листинг одного хода собираются в одну свёрнутую карточку со строкой вида Explored · 5 ops · Read main.kt +4, а тап по ней разворачивает список операций. Пока агент ещё работает, карточка подписана Exploring и показывает значок обновления вместо галочки.
- Карточка плана прячется, пока план живёт в полоске плана. Как только план предложен, одобрен или выполняется, в ленте его дубликата нет — он нарисован внизу. В ленте планы остаются только завершённые, из прошлых ходов. Если пришёл план-дубликат того же хода, остаётся самый полный, а остальные убираются.
- Открытые вопросы стоят внизу, а закрытые остаются в истории на своих местах. Вопрос, на который вы ответили или который просрочился, остаётся в ленте обычной карточкой в той позиции, где он появился.
- Тап по пустому месту ленты убирает клавиатуру. Это быстрый способ вернуть полноэкранный вид чата, не закрывая ничего.
- Кнопка возврата вниз всплывает над лентой. Она появляется, когда вы ушли вверх, а раскрытая панель плана закрыта, и показывает счётчик нового.
Прокрутка и счётчик нового
- Лента сама следует за новыми сообщениями, пока вы внизу. Как только вы ушли вверх, автоследование выключается, а новые карточки продолжают приходить в ленту.
- Пока вы читаете, лента не двигается сама. Ваш жест главнее любой программной прокрутки: пока палец на ленте, лента не двигается вовсе, а позицию держит элемент, на котором вы остановились. Новые карточки в хвосте докручиваются у Compose по ключу этого элемента, поэтому чтение не «уезжает» под стримингом. Приложение не трогает позицию чтения само — любая программная прокрутка здесь отменяла бы ваш жест; если раскладка всё же разъехалась, сдвиг виден в диагностике
SCROLL_DRIFT. - Лёгкий сдвиг у самого низа автоследование не выключает. Мёртвая зона — 24dp, так что случайный свайп на пару пикселей ничего не ломает.
- Программная прокрутка не считается вашим жестом. Возврат к хвосту, восстановление позиции и подгрузка старых карточек не выключают слежение за лентой.
- Счётчик нового считает только содержательные карточки. В счётчик входят ответы агента, его мысли и планы, а команды, служебные строки и карточки файлов не входят. Счётчик обнуляется, когда вы вернулись вниз.
- Цифра на кнопке ограничена. Дальше 99 она показывается как 99+, а если нового ничего нет, цифра не рисуется вовсе.
- После подгрузки старого и во время активного хода лента не прыгает. Приложение запоминает элемент, на котором вы остановились, и держит его на месте, даже если выше или ниже добавились карточки.
Догрузка истории
- В начале в ленте лежит не вся история, а последние элементы. При входе в тред показывается хвост из 300 элементов, остальное приложение читает отдельно.
- Кнопка load older стоит в самом верху ленты и появляется, только когда выше ещё есть что показать. Пока идёт загрузка, вместо надписи рисуется маленький кружок.
- Догрузка включается сама. Когда вы докрутили почти до конца загруженного или упёрлись в край, приложение само просит следующую страницу, так что кнопку нажимать не обязательно.
- Сначала история дочитывается с телефона, и только потом из сети. Простое нажатие на кнопку отвечает мгновенно, а если в памяти телефона ничего не осталось, приложение идёт за страницей к агенту.
- Возвращение к нижнему краю урезает ленту обратно. Если история уехала далеко вверх, а вы вернулись к последнему сообщению, окно снова схлопывается до последних элементов.
Пустые состояния
- Пока тред открывается, вместо ленты мигают заглушки. Это семь полосок разной ширины, которые плавно проявляются и гаснут, чтобы было видно, что приложение грузит историю.
- Если открыть тред не удалось, в центре пишется причина и кнопка Retry. Для нового диалога Retry создаёт его заново, для существующего — переоткрывает.
- Пустой тред показывает папку и статус. Крупно написано имя папки, под ним две метки: какая модель отвечает и в каком состоянии связь — connected, connecting, error или offline. Ниже мелким шрифтом написан полный путь к папке. Больше в этом состоянии ничего не рисуется: лента остаётся пустой, пока вы не отправите первое сообщение.
- Когда агент отвечает, а история ещё пустая, заглушка не показывается. Пустое состояние рисуется только когда хода нет.
Технические данные
| Параметр | Значение |
|---|---|
| Элементов в ленте при входе в тред | 300 последних |
| Элементов добавляется за одну догрузку | 100 |
| Бюджет чтения при входе | 400 элементов, то есть хвост плюс одна страница |
| Ходов в одной серверной странице старого | до 3 |
| Максимум страниц за одну догрузку | 4 |
| Порог автодогрузки | дошёл до последних 6 элементов загруженного или упёрся в край ленты |
| Мёртвая зона у низа ленты | 24dp |
| Порог диагностики позиции чтения | 1px (позиция — пара firstVisibleItemIndex + firstVisibleItemScrollOffset; при чтении ленту не двигаем вообще) |
| Потолок счётчика нового | 99+, дальше показывается 99+ |
| Что попадает в счётчик нового | ответ агента, мысли и планы |
| Что в счётчик не попадает | команды, служебные строки и карточки файлов |
| Порог полосы лимитов | 70% расхода, кнопка Switch от 80% |
| Цвета процента расхода контекста | до 70% бирюзовый, от 70% жёлтый, от 90% красный |
| Порог баннера залипания кэша | три запроса подряд дали меньше половины входа из кэша, при этом запрос крупный (от 30 тысяч токенов), вход не упал ниже 60% прошлого и между запросами не было паузы дольше 30 минут |
| Сколько живёт строка веб-поиска после завершения | 5 секунд |
| Когда есть полоса расхода контекста | нужны данные о токенах и непустая лента, при полном нуле цифр полоса не рисуется |
| Сколько заглушек в скелете входа | 7 полосок |
| Отступ между карточками | 8dp |
| Время докрутки цифр на полосе контекста | 600 мс, если значение растёт, и 300 мс, если уменьшается |
Что агент умеет
- Агент может писать в ленту сколько угодно карточек за один ход, и они появляются сразу по мере поступления, а не в конце.
- Агент может разбить свои действия на серии чтения, поиска и листинга, и лента сама соберёт их в одну свёрнутую карточку с итогом.
- Агент может присылать размышления несколькими кусками, и лента склеит их в один блок с общей длительностью.
- Агент может завести вопрос к вам или попросить одобрение действия, и тогда лента останавливается и ждёт вашего ответа.
- Агент может работать с субагентами и другими тредами, и его действия в этом отношении видны в ленте отдельными карточками со ссылкой на нужный тред.
- Агент может прислать картинку, список изменённых файлов, сведения о сжатии контекста и итоговую сводку по ходу, и всё это попадает в ленту отдельными карточками.
Ограничения
- Лента не хранит всю историю треда в памяти: в любой момент прорисовано только окно в несколько сотен элементов, а всё остальное лежит в хранилище телефона и подгружается по требованию.
- Считать новое можно только пока вы ушли вверх: у низа ленты счётчик не нужен, потому что новые карточки и так перед глазами.
- Плашка «агент не отвечает» прокручивается вместе с лентой, поэтому при длинном ответе агента и быстрой прокрутке её можно пролистать, не заметив.
- План, который сейчас выполняется, виден только в полоске плана под лентой: в самой ленте его нет, и в истории он появится только когда ход закончится.
- Загрузить старую историю можно только вверх по ленте: кнопки догрузки вниз нет, а при возвращении к хвосту окно схлопывается.
- Если агент долго молчит, лента честно показывает плашку с минутами молчания, но причину молчания она не знает и угадывать её не пытается.