Раздел: AI & Agents
Идентификатор:webai_chat· Экран:ui/webai/(17 файлов, ~5400 строк)
Снимок: 2026-10-11
1. Зачем и что это
Отличие от других чатов проекта:
| Чат | Агент | Что умеет |
|---|---|---|
ui/opencode | opencode | сессии, файлы проекта, инструменты |
ui/codex | Codex app-server | MCP, скиллы, планы, кэш ленты |
WebAI (ui/webai) | модель напрямую через backend | картинки/документы, речь, поиск, шаблоны |
Весь чат живёт на backend: аккаунты, диалоги, сообщения, группы и шаблоны хранятся там, поэтому чат синхронизируется и переживает переустановку приложения. По умолчанию выбрана модель deepseek-v4-flash.
2. Как попасть
Хаб инструментов → секция AI & Agents → карточка «Чат WebAI» (hub_webai_chat_title, подпись «Мультипровайдерный чат с картинками и документами», тайл «AI-чат»).
| Откуда ещё | Путь |
|---|---|
| Список чатов | Screen.WebAiChats → WebAiChatsScreen.kt |
| Мост | app_navigate с screen: "webai_chat" |
Маршрут: Screen.kt:129 ("webai_chat" -> Screen.WebAiChat) → MainRouter.kt:282.
3. Экран: элементы и действия
| Элемент | Что делает | Где в коде |
|---|---|---|
| Шапка | Название чата, кнопки: группы, шаблоны, экспорт, переименование | ui/webai/WebAiHeader.kt |
| Лента | Сообщения пользователя и модели, «размышления», источники, вложения | WebAiChatRow.kt, WebAiBubbles.kt |
| Поле ввода | Текст + кнопка отправки/остановки | ui/webai/WebAiInputBar.kt |
| Чипы модели | Выбор модели из providersWithModels | ui/webai/ModelPicker.kt |
| Тумблеры Thinking и Search | Рассуждения и веб-поиск; состояние запоминается | toggleThinking, toggleSearch, toggle*Persist |
| Кнопка вложения | Пикер файлов/картинок, до 4 штук | addAttachment, ui/webai/WebAiFiles.kt |
| Кнопка озвучки | Озвучить сообщение модели | toggleSpeech, ui/webai/WebAiSpeech.kt |
| Диалоги | Переименование, удаление, восстановление после ошибки | ui/webai/WebAiChatDialogs.kt |
| Лист шаблонов | CRUD шаблонов промптов | ui/webai/WebAiTemplatesDialog.kt |
| Лист сессий | Переключение между диалогами | ui/webai/WebAiSessionsSheet.kt |
Действия над сообщениями: regenerate() — переспросить у модели, retry() — повторить после ошибки, editMessage(index) — вернуть текст пользовательского сообщения в поле ввода для правки, stop() — прервать стриминг.
4. Возможности подробно
Стриминг
send() собирает вложения, sendInternal() добавляет сообщение пользователя и пустой «pending» ответ, выставляет streaming = true и запускает streamJob. Текст ответа и текст рассуждений копятся в отдельные StringBuilder, а updateAssistant точечно обновляет элемент ленты. Пока идёт стриминг, отправка нового сообщения блокируется (if (ui.streaming) return), а кнопка ввода превращается в «стоп».
Поля состояния стриминга: streaming, streamingStartedAt, searching — последнее показывает отдельную фазу поиска, чтобы лента могла показать «ищу…» независимо от генерации.
Модели и провайдеры
Модель выбирается через ModelPicker из карты providersWithModels, пришедшей с сервера. Провайдер определяется функцией webAiProviderForModel — это зеркало webai.ProviderFor на бэкенде:
| Префикс модели | Провайдер |
|---|---|
qwen… | qwen |
kimi…, moonshot… | kimi |
glm…, zai-… | zai |
| остальное | deepseek |
При этом webAiVisibleProviders = setOf("deepseek"): в интерфейсе виден только DeepSeek, а webAiVisibleModel тихо сводит любую чужую модель к deepseek-v4-flash. Остальные провайдеры существуют на backend (аккаунты, ProviderFor), но пока скрыты из UI — это осознанный переключатель.
Вложения
Правила целиком в ui/webai/WebAiFiles.kt:
- Лимит — 4 файла на сообщение (
MAX_CHAT_ATTACHMENTS), тот же предел продублирован на бэкенде; - Размер одного файла — до 10 МиБ (
MAX_WEBAI_UPLOAD_BYTESвutil/ImagePrep.kt), чтение ограничиваетсяreadBounded, а не чтением в память целиком; - картинки:
jpg,jpeg,png,gif,webp,bmp(по MIME или расширению); - документы:
pdf,doc,docx,txt,md,csv,xls,xlsx; - вложения принимает только DeepSeek (
webAiAttachmentAllowed), остальные провайдеры — отказ с подсказкой «does not accept files»; - картинки перед отправкой готовятся (
webAiPreparedUploadName,webAiMimeForExtension), расширение итогового файла приводится в соответствие с реальными байтами:webpостаётсяwebp, всё остальное по умолчанию становитсяjpeg; - документу без расширения оно добавляется по MIME (
webAiUploadNameForMime); - байты уходят в
uploadsбэкенда, в ленте остаётсяpath; - текст сообщения может быть пустым, если приложены картинки.
filesEnabled() — двойная проверка: провайдер должен принимать файлы и должен существовать хотя бы один активный аккаунт этого провайдера. Без аккаунта кнопка вложения даст понятную ошибку, а не тихую отправку.
Диалоги, группы, шаблоны
loadChats(openLast)— список диалогов, опционально открыть последний;newSession(),openChat(chat),renameCurrentChat,renameChat,deleteChat,togglePinned(закрепление);- группы:
createGroup,renameGroup,deleteGroup,moveChatToGroup; - шаблоны промптов:
createTemplate,updateTemplate,deleteTemplate,loadTemplates; - авто-заголовок:
generateTitle(chatId)просит backend назвать диалог по первому сообщению (POST …/auto-title) и подставляет результат в список и в шапку. Ошибка уходит в лог, а не в UI.
Озвучка
toggleSpeech(item) озвучивает сообщение модели и запоминает speakingMessageId, чтобы кнопка у нужной реплики показывала активное состояние; stopSpeech() прерывает. Голос — через ui/webai/WebAiSpeech.kt.
Экспорт
ui/webai/WebAiChatExport.kt — выгрузка диалога в файл; кнопка в шапке.
5. Данные: БД, файлы, настройки
| Что | Где лежит | Как меняется | Кто читает |
|---|---|---|---|
| Аккаунты | ~/.y83-ws/ws-backend.db (backend) | POST/PUT /api/v1/webai/accounts | filesEnabled, модели |
| Диалоги и сообщения | та же БД backend | /api/v1/webai/chats* | loadChats, лента |
| Группы и шаблоны | та же БД | /chat-groups, /templates | Листы секций |
| Загруженные файлы | uploads бэкенда | uploadAttachment | Модель при ответе |
| Состояние чипов | prefs приложения | toggleThinkingPersist, toggleSearchPersist | Следующий запуск |
| Логи ошибок | filesDir/log.txt | FileLog.log("webai …") | dev_log {source:"app"} |
6. Backend: REST/роуты
Все пути — backend/internal/api/v1/routes.go, обработчики wa (webai).
| Метод и путь | Назначение |
|---|---|
GET /api/v1/webai/accounts | список аккаунтов (для проверки filesEnabled) |
GET /api/v1/webai/providers | провайдеры и модели для ModelPicker |
GET /api/v1/webai/chats | список диалогов |
POST /api/v1/webai/chats | создать диалог |
GET /api/v1/webai/chats/{id}/messages | сообщения диалога |
POST /api/v1/webai/chats/{id}/stream | стриминг ответа существующего диалога |
POST /api/v1/webai/chats/new/stream | стриминг в новом диалоге |
POST /api/v1/webai/chats/{id}/auto-title | авто-заголовок |
PUT /api/v1/webai/chats/{id} | переименование/закрепление |
DELETE /api/v1/webai/chats/{id} | удалить диалог |
GET/POST /api/v1/webai/chat-groups, PUT/DELETE …/{id} | группы диалогов |
GET/POST /api/v1/webai/templates, PUT/DELETE …/{id} | шаблоны промптов |
GET /api/v1/webai/chats/{id}/messages/{messageId}/files/{index}/bytes | байты вложения для показа |
7. Мост и MCP
| Что | Как | Примечание |
|---|---|---|
| Открыть экран | app_navigate с screen: "webai_chat" | MainScreen.kt:190 |
| Список чатов | app_navigate с screen: "webai_chats" | MainScreen.kt:189 |
| Спросить модель | MCP ai_ask {message, model, provider, thinking, template, search} | Скилл y83-webai |
| Статус аккаунтов | MCP ai_status | квоты, ошибки, PoW |
Важно: ai_ask — это не интерфейс этого чата, а прямой вызов модели. Через него не видно ни групп, ни шаблонов, ни вложений из UI; наоборот, диалоги, созданные в чате, и вызовы ai_ask живут в разных слоях backend.
8. Настройки, конфиг, квоты и лимиты
| Параметр | Значение | Где задаётся |
|---|---|---|
| Модель по умолчанию | deepseek-v4-flash | WebAiChatUiState.model |
| Вложений на сообщение | 4 | MAX_CHAT_ATTACHMENTS |
| Размер вложения | 10 МиБ | MAX_WEBAI_UPLOAD_BYTES |
| Видимые провайдеры | только deepseek | webAiVisibleProviders |
| Thinking/Search | включены по умолчанию | WebAiChatUiState |
| Название нового чата | «New chat» | То же |
Квота аккаунтов (100/день, retry 3, backoff 30 с при 429) относится к инструменту «Аккаунты WebAI» и общему пути ai_ask.
9. Ловушки и ограничения
- Только DeepSeek в UI. Выбрать qwen/kimi/zai в чате нельзя:
ModelPickerпоказывает только модели видимых провайдеров, а чужая модель из ответа сервера сводится кdeepseek-v4-flash. - Вложения требуют активного аккаунта. Проверка
filesEnabledучитывает не только провайдера, но и наличие рабочего аккаунта — при мёртвых аккаунтах кнопка не работает даже при «правильной» модели. - Лимит 4 и 10 МиБ дублируются на сервере. Обойти клиентский лимит нельзя, но и наоборот: если backend ужесточит предел, приложение об этом не узнает — придёт ошибка в ленту.
- Формат картинок нормализуется. Расширение после подготовки может не совпасть с исходным (кроме webp), и MIME меняется на
image/jpeg— файлы, чувствительные к расширению, лучше слать PNG осознанно. - Рассуждения и поиск — отдельные фазы. Пока идёт
searching, контент может ещё не появляться: это нормально, не «зависло». - Название диалога появляется не сразу: авто-заголовок генерируется сервером и подставляется позже, поэтому в списке сначала будет «New chat».
- Правка сообщения пользователя не переспрашивает модель сама —
editMessageтолько возвращает текст в поле; повтор нужно отправить руками.
10. Источники правды
app/src/main/java/com/y83/ws/ui/webai/WebAiChatViewModel.kt— состояние, стриминг, вложения.app/src/main/java/com/y83/ws/ui/webai/WebAiFiles.kt— правила провайдеров, форматов, лимитов.app/src/main/java/com/y83/ws/ui/webai/WebAiChatScreen.kt,WebAiHeader.kt,WebAiInputBar.kt,ModelPicker.kt— UI.app/src/main/java/com/y83/ws/ui/webai/WebAiBubbles.kt,WebAiChatRow.kt,WebAiThinking.kt— лента.app/src/main/java/com/y83/ws/ui/webai/WebAiChatsScreen.kt,WebAiSessionsSheet.kt,WebAiTemplatesDialog.kt,WebAiChatDialogs.kt— списки и диалоги.app/src/main/java/com/y83/ws/ui/webai/WebAiSpeech.kt,WebAiChatExport.kt,WebAiHeader.kt— озвучка и экспорт.app/src/main/java/com/y83/ws/data/api/WebAiDtos.kt,BackendApiWebAi.kt— клиент REST.backend/internal/api/v1/routes.go:109–140— роуты webai.app/src/main/res/values/strings_hub.xml— подписи карточки хаба.