Плавающий оверлей
Круг-микрофон поверх приложений, режимы «Общие», «Диалог» и «Найти в интернет», панель чата и окна ответов.
- overlay
- ui
y83 projects / 02 / android + go + llama
работает · 2026
Мэри живёт на телефоне: оверлей принимает голосовые команды, Go-сервер в Termux маршрутизирует их между локальной моделью, облачным провайдером и WebAI, а собственный мост внутри APK читает дерево экрана, рисует номера поверх кликабельных узлов и выполняет действия. 34 навыка срабатывают по шаблонам без модели, автороутер добирает остальное коротким запросом. Локальный QVikhr работает через llama-server и OpenCL на Adreno 825; облачный путь переключается без перезапуска приложения.
продукт
Сделать ассистента, который одинаково хорошо работает без root и без обязательного облака: слышит, отвечает голосом, видит экран, управляет приложениями и умеет искать информацию в интернете.
Основной путь остаётся локальным: QVikhr 1.5B на llama-server, голос Silero и мосты внутри APK. Но модель больше не единственная: ModelHub переключает чат между локальной моделью и любым OpenAI-совместимым API-провайдером, а WebAI/DeepSeek остаётся отдельным слоем для разговоров, поиска и vision-запросов.
Мэри не обещает, что локальная модель сама разберётся в любом интерфейсе. Быстрые команды уходят навыкам до генерации, а модель получает только очищенный контекст и список интерактивных узлов.
Три слоя и один контракт. Оверлей отвечает за голос, окна и рисунок поверх экрана; Go-сервер ведёт навыки, движки, память и браузер; мост внутри APK — за доступность и действия. Что делать на текущем экране, решает сервер: он отдаёт APK состояние, тихий режим, разрешение рисовать номера, длительность окна прослушки, уровень приглушения медиа и доступные команды.
возможности
Основное — не отдельная команда, а соединение голоса, памяти, модели и управления интерфейсом.
Круг-микрофон поверх приложений, режимы «Общие», «Диалог» и «Найти в интернет», панель чата и окна ответов.
Горячее окно SpeechRecognizer и VAD: 70–150 мс до готовности, тишина закрывает прослушивание без повторных попыток.
Карточка печатает текст по словам синхронно с фактической длительностью Silero, а не по оценке.
Поверх видео, Shorts и музыки Мэри уважает контекст: не перебивает звук, а переходит в текст или приглушает только окно ответа.
Внешний llama-server поднимает модель в Termux, определяет реальный GPU-бэкенд по /proc и сам останавливает её после простоя облачной ветки. На Adreno 825 OpenCL ускоряет prefill с 42 до 344 t/s — восьмикратно к CPU.
19 пресетов OpenAI-совместимых провайдеров, включая свой адрес: discovery моделей, проверка ключа, флаги vision/reasoning и отдельный лимит контекста.
Отдельный слой аккаунтов с очередью, лимитами, контекстным разговором и локальным OpenAI-совместимым /v1 для внешних клиентов.
Один resolver собирает клиент для голоса, чата и браузерного агента; при недоступном облаке чат возвращается к локальной модели.
ControlBridgeService работает как служба доступности и держит свой HTTP-мост: 14 действий — tap и long-press, свайп, ключи, click/tap/long-click/scroll-to по узлу, ввод текста, открытие приложения, URI и браузера, Intent, буфер обмена. На стороне Go — 22 маршрутов /api/control.
Сервер ищет узел по id, тексту, описанию или классу, делает hit_test по координатам, ждёт появления узла и подписывает его; действие идёт по узлу, а не по пикселю.
control/read отдаёт плоский список с ролями, состояниями и позициями, control/semantic — короткое семантическое представление; журнал действий хранит, что и когда нажали.
Оверлей рисует номера кликабельных узлов поверх интерфейса — как подсказка выбора в YouTube. Достаточно сказать «нажми 3»: сервер берёт тот же список и тапает нужный узел, а после проверки по фингерпринту говорит, изменился ли экран.
SSE-поток /api/control/effect/stream присылает кадр в момент действия: оверлей рисует тап-кольцо со свечением или свайп «пальцем» синхронно с движением, а не с задержкой опроса.
Сервер сам решает поведение: состояние экрана, тихий режим, разрешение рисовать номера, озвучка ответов, длина окна прослушки, уровень приглушения медиа и список доступных команд. APK не угадывает сценарий, а исполняет контракт.
Видимый браузер получает очищенное дерево страницы, LLM выбирает web_tap, web_type, web_scroll_to, web_navigate или web_done, а fingerprint сравнивает страницу до и после: два одинаковых состояния подряд останавливают цикл.
Детектор отмечает captcha и challenge и не выдаёт их за результат — сценарий останавливается с понятной причиной, а не тыкает в страницу бесконечно.
Скрытый WebView идёт к DuckDuckGo и читает страницы; AgentSearch, SearXNG, Google CSE и Яндекс подключаются как внешние движки, когда заданы ключи.
Погода, солнце, валюты, новости, время, калькулятор, перевод, напоминания, буфер обмена, уведомления, скриншот, карты, открытие приложений, YouTube и управление голосом — каждое срабатывает по шаблону, без обращения к модели.
Если шаблоны не совпали, один короткий запрос к активной модели возвращает JSON с навыком, аргументами и уверенностью. Ниже порога 0.6 решение отбрасывается, и фраза идёт обычным диалогом — роутер не угадывает.
Сработавший навык и его аргументы держатся 15 минут: после «погода в Варшаве» фраза «а послезавтра?» досылает недостающий город в тот же навык.
Команда вида «нажми/открой/введи», которую не поймал ни один шаблон, уходит в UI-фолбэк: очищенный список кликабельных узлов → локальная модель → одно JSON-действие (tap, type, back, home, swipe-up). Результат сверяется по фингерпринту экрана.
Облачные модели получают tools API: до трёх раундов вызовов, WebAI — локальный OpenAI-совместимый эндпоинт.
Пин «запомни, что…», действия, вопросы и ответы попадают в локальную память; релевантное подмешивается через BM25.
Записи дня сжимаются локальной моделью в темы и итог, последние семь дней остаются доступными для вопросов о прошлом.
Голосовая команда разбирает «через 10 минут» и «завтра в 9», а список и отмена доступны в настройках.
ControlBridge, WebBridge, NotifBridge и BridgeWatchdog восстанавливают соединение после вылета клиента или агента.
как работает
Оверлей или WebView-чат передают команду Go-серверу в Termux.
Регулярки и LLM-роутер выбирают навык, режим поиска или диалога.
EngineResolver собирает клиента локальной модели, API-провайдера или WebAI.
Модель получает локальный контекст и вызывает навык, телефон, браузер или чтение экрана.
Действие сверяется по узлу, fingerprint страницы или ответу инструмента.
Результат стримится, пополняет память и озвучивается Silero.
результаты
строк Go + Kotlin
36 579 Go (30 439 без тестов) и 12 919 Kotlinмаршрутов API
REST, SSE и локальный OpenAI-совместимый /v1движка
локальная QVikhr, API-провайдер и WebAI — переключение без перезапускатестовых файлов
6 140 строк тестов; 35 файлов Kotlin и 5 682 строк внутренней документациибыстрых навыка
срабатывают по шаблонам без модели; выше порога 0.6 подключается автороутермаршрута управления
14 действий моста: узлы, ввод, свайпы, приложения, уведомления и буферЦифры сверены с кодом проекта 2026-10-14.
экраны
Основные состояния и интерфейсы, которые видны пользователю во время работы.
листайте вбок · 8 экранов
Плавающий микрофон
состояния слушаю, думаю, говорю
Голосовая карточка
ответ печатается в такт Silero
Текстовый чат
история, стриминг, Markdown и сноски
Хаб моделей
локальная модель или API-провайдер
Браузерный агент
дерево страницы и следующий шаг
Панель управления
узлы, уведомления, приложения и журнал
Номера на экране
нумерация кликабельных узлов поверх интерфейса
Журнал действий
что нажали, когда и чем закончилось
технологии
этапы
От голосового оверлея к единому агенту с тремя движками, памятью и браузерными действиями.
распознавание, озвучка, режимы и чат
мост доступности, семантическое чтение, дневники
внешний llama-server и OpenCL на телефоне
провайдеры, discovery моделей, WebAI и автоостановка локальной модели
семантическое дерево, действия и fingerprint для веб-сценариев
вопросы
Нет. Локальный QVikhr на llama-server — базовый режим, но ModelHub также выбирает OpenAI-совместимого провайдера, а WebAI/DeepSeek остаётся отдельным слоем для разговоров и поиска. При недоступном облаке чат возвращается к локальной модели.
ControlBridgeService работает внутри APK через AccessibilityService: отдаёт дерево доступности, принимает действия по узлам и возвращает семантическое состояние экрана. Внешний MCP остаётся альтернативным источником управления.
Видимый браузер отдаёт очищенное дерево и снимок изменения состояния. LLM выбирает действие из ограниченного набора, после чего fingerprint сравнивает страницу до и после. Одинаковая страница два раза подряд останавливает сценарий.
Оверлей нумерует кликабельные узлы текущего экрана и держит номера поверх интерфейса, пока список свежий. Команда «нажми 3» берёт тот же список и нажимает нужный узел через мост; результат проверяется по фингерпринту — если экран не изменился, Мэри об этом скажет.
Сервер. Он отдаёт APK контракт текущего экрана — состояние, тихий режим, разрешение рисовать номера, озвучку, длину окна прослушки, уровень приглушения медиа и доступные команды. Клиент исполняет контракт, а не угадывает сценарий: правила живут в одном месте.
Быстрые команды («погода», «поставь напоминание») дешевле и точнее выполнить по шаблону, чем гонять модель. Автороутер подключается только там, где шаблоны не совпали: один короткий запрос, JSON с навыком и уверенностью, порог 0.6. Ниже порога фраза идёт обычным диалогом.
Внешняя llama-server живёт в Termux под runit и может быть остановлена как сервис. Если активна облачная модель и локальную не использовали 10 минут, ModelService гасит её, чтобы не греть GPU; возврат к локальной модели поднимает сервис через Termux или кнопку запуска.
Мэри
Мэри — голосовой ассистент и браузерный агент для Android. Базовый контур живёт на телефоне: она слышит и отвечает голосом, читает дерево экрана, рисует номера поверх кликабельных узлов, управляет телефоном через собственный мост и выбирает один из трёх движков — локальную QVikhr, API-провайдера или WebAI/DeepSeek.