- заведён каталог web/ — Vue 3, роутер пятой версии, сборка Vite; собранное вшивается через go:embed и раздаётся корневым маршрутом: разметка на неизвестном пути вне корней сервиса, отказ контракта внутри корня - перечень корней сервиса стал единой точкой и порождает регистрацию маршрутов, а не описывает её; журнал раздачи пишет исход и длину пути, но не сам путь - шаг front зовёт Node контейнером docker — Biome, юнит-тесты Vue и сборка входят в гейт, а в Dockerfile появилась ступень приложения
12 KiB
Паспорт проекта
Зачем это и для кого. architecture.md отвечает «как устроено», tasks/BACKLOG.md — «в каком порядке», паспорт — «зачем и для кого».
Цель
Превращать записанную речь в текст, который можно читать и искать, и хранить этот текст вместе с записью.
Сервис — архив, и это решено 2026-08-11. Прежде граница читалась «отдаём текст и на этом заканчиваем»; теперь расшифровки и исходные записи лежат бессрочно, а список отбирается по темам. Причина в основном сценарии: семейный архив загружают один раз, а возвращаются к нему годами.
Потребители — список закрытый: он определяет, что считать нужным, а что интересным.
| Кто | Что ему нужно от нас |
|---|---|
| Владелец сервиса | Загрузить диктофонную запись или видео из семейного архива с телефона и получить текст. Видеть, кто сколько загрузил и во что это обошлось |
| Приглашённый пользователь | Войти в приложение через свою учётную запись, загрузить запись, забрать текст, вернуться к ней через месяц. Приложение ставится на телефон; каждый видит только свои записи |
| Внешняя программа | Отдать файл по HTTP, представившись своим токеном, и опросить готовность. Сегодня почти не работает: приём и опрос закрыты сессией OIDC, а своего токена у программы нет — годится только чужая сессия, снятая из браузера и предъявленная кукой либо заголовком Authorization. Токен приносит api-tokens |
Вход у сервиса один — HTTP API, и приложение строится поверх него. До 2026-08-11 основным входом был Telegram-бот. 2026-08-11 основным объявили приложение: диктофонная запись на несколько часов через Telegram не проходит вовсе. 2026-08-14 бот убран целиком — временно, до задачи, которая свяжет чат с учётной записью. Вместе с ним из потребителей ушёл пользователь Telegram.
Цель достигнута, когда:
- запись любого распространённого формата принимается без предварительной подготовки, включая дорожку из видео;
- запись расчётного потолка — шести часов — доходит до текста, а не прерывается
ошибкой при достижении предела (норма —
openspec/specs/storage); - сервисом пользуются несколько человек, и записи одного не видны другому;
- текст доступен там же, где загружали. Человек узнаёт о его готовности, не держа приложение открытым;
- расшифровка не теряется: к записи возвращаются через месяц и находят её по заголовку и темам;
- владелец видит расход по каждому пользователю и понимает, во что обходится приглашение ещё одного человека.
Что целью не является
Граница домена. По ней в теме architecture судят, не перенесено ли понятие
через границу.
- Правка текста руками. Машинную вычитку расшифровки отдаём — литературный текст стоит рядом с сырым, — а редактором не становимся: текст руками не правим, не размечаем и не экспортируем в форматы документов. Граница сдвинута 2026-08-11: до того запрет читался «расшифровку отдаём как есть».
- Разговор о записи. Ответы на вопросы по содержанию и поиск по смыслу — за границей. Заголовок, пересказ и темы внутри границы: она сдвинута 2026-08-10, и до того запись читалась «мы отдаём текст, а не выводы из него». Считать уровни текста берётся задача llm-insights-adapter.
- Собственные модели. Не обучаем и не держим у себя ни модель распознавания, ни языковую модель: и речь, и выводы из текста считает внешний сервис.
- Управление учётными записями. Пользователей заводит и проверяет внешний провайдер, свою регистрацию и свои пароли не делаем. Одно исключение появилось 2026-08-11 вместе с решением про PocketBase: в панель администратора владелец входит своим паролем, потому что подпустить к ней внешнего провайдера PocketBase не даёт.
- Живая расшифровка. Работаем с готовой записью, поток в реальном времени не обрабатываем.
- Диктофон. Запись звука делает телефон, а приложение принимает готовый файл. Своей записи и работы без сети не делаем.
- Файловое хранилище общего назначения. Храним аудио и видео, отданные ради
речи в них. Складом произвольных файлов и папками сервис не становится. Общего
доступа к чужим записям целью нет, и с 2026-08-14 его нет и на деле: у записи
есть владелец, и чужую по её идентификатору не отдают
(security.md, «Периметр»). Закрыла это задача
record-ownership. - Учёт денег. Считаем объём, минуты и токены по каждому пользователю и показываем их владельцу. Цен, счетов и отказов по исчерпании квоты не делаем: пользователя, потратившего слишком много, останавливает разговор или отзыв доступа в Authelia.
Типовые сценарии
Первые два — основные, и сегодня не работает ни один. Приложение с 2026-08-15
есть, но экранов у него пока нет: оно открывается и показывает вошедшего, а
загрузку и список заводят upload-and-status-screen и records-list-screen.
- Семейный архив. Человек открывает приложение на телефоне, выбирает до десяти записей разом — диктофонные дорожки и видео, — и закрывает его. Загрузка показывает ход. Файл, который уже загружали, не грузится второй раз. Когда текст готов, приходит уведомление; в списке запись видна заголовком и темами.
- Возвращение к записи. Через месяц человек открывает список, находит запись по заголовку или теме и читает вычитанный текст, а при нужде — сырую расшифровку.
- Загрузка по HTTP. Программа шлёт
POST /app/audiorecordsсо своим токеном, получает идентификатор записи и читает её карточкуGET /app/audiorecords/{id}, пока не увидитdone; текст забирает отдельным адресомGET /app/audiorecords/{id}/text. Сегодня доступно только предъявившему сессию OIDC: анонимный запрос всеми адресами отклоняется. Своего входа у программы нет — его заводитapi-tokens. Записи при этом разграничены: программа с чужой сессией видит только записи того, чью сессию предъявила. - Отказ на середине. Конвертация или распознавание не удались — запись
получает признак остановки с причиной, и карточка записи отдаёт признак и
причину тому, кто её загрузил. Сообщения о неудаче сервис никому не шлёт:
доставка ушла вместе с ботом, а уведомления заводит задача
ntfy-delivery.
Референсы
Где смотреть prior art, когда упёрлись.
- Yandex SpeechKit, отложенное распознавание — модель
deferred-general, которой пользуемся: она и задаёт потолок по длине записи и формату. - Whisper и его серверные обёртки — запасной путь, если внешний сервис
перестанет устраивать по цене или по качеству русской речи.
PocketBase из референсов ушла: она больше не кандидат — в стек её перевела
задача
pocketbase-storage2026-08-12 (adr); там она держит хранилище, файлы и панель владельца. Схема и раскладка — database.md. Учётные записи она хранит и получает от Authelia своим провайдером OIDC, но источником их не становится: заводит и проверяет людей по-прежнему Authelia.