У меня есть Plaud Note — диктофон с AI, который записывает все встречи и разговоры. Каждый день через него проходит куча задач, идей, решений. Проблема: всё это оседает в текстовых транскрипциях и никуда не попадает. Задачи теряются, идеи забываются, решения не фиксируются. Нужна система, которая сама разберёт голосовой поток на атомарные элементы — задачу в таск-трекер, идею в банк идей, решение в лог.
Так родился Voxium — voice-first AI система управления задачами. 16 февраля я сел и собрал полный MVP за один вечер. 19 коммитов, ~2 часа работы, работающий продукт на боевом домене.
Стек и архитектура
Выбор стека был быстрым — взял то, что уже обкатано на других проектах, плюс добавил специфичные для голосовых штук инструменты.
| Компонент | Технология | Почему |
|---|---|---|
| Backend | Django 5.1 + DRF | Быстрая разработка, ORM, админка из коробки |
| Frontend | React 18 + Vite 5 + Tailwind CSS v4 | Скорость сборки, современный стек |
| База | PostgreSQL 16 + pgvector | Векторный поиск для банка идей |
| Очереди | Celery + Redis 7 | Асинхронная обработка голоса |
| AI | OpenAI GPT + Instructor | Structured output из транскрипций |
| Auth | SimpleJWT | Токены без лишней инфраструктуры |
Всё крутится в Docker Compose на выделенном сервере. Порты в диапазоне 30xxx, чтобы не конфликтовать с другими проектами на том же хосте.
graph LR
A[Plaud Note] -->|webhook| B[Django API :30000]
B -->|task| C[Celery Worker]
C -->|GPT| D[OpenAI API]
D -->|structured| C
C -->|save| E[(PostgreSQL + pgvector)]
F[React :30173] -->|REST/WS| B
G[Telegram Bot] -->|commands| B
H[nginx :443] -->|proxy| B
H -->|proxy| F
Backend: модели, API, голосовой пайплайн
Модели данных
Первым делом — модели. Получилось 9 штук: Workspace, Area, Project, Section, Task, VoiceCapture, Idea, Decision, ActivityLog. UUID primary keys, ForeignKey с каскадным удалением.
Task — центральная модель. Статусы: inbox, todo, in_progress, done, cancelled. Приоритеты: urgent, high, medium, low. Энергия: high_focus, routine, low_energy — это для умной фильтрации: утром показывать задачи на свежую голову, вечером — рутину.
Есть поля для AI: confidence_score (насколько AI уверен в извлечённой задаче), source (voice/manual/telegram), raw_transcript. Idea хранит embedding через pgvector — потом можно искать похожие идеи по семантике.
JWT Auth и Task API
Три эндпоинта авторизации: регистрация (автоматически создаёт Workspace), логин, рефреш токена. REST API для задач через DRF ModelViewSet — фильтрация по статусу, приоритету, энергии, проекту. Поиск по тексту. Все запросы скоупятся на workspace текущего пользователя.
Голосовой пайплайн
Самая интересная часть. Plaud Note шлёт webhook на /api/voice/webhook/ с транскрипцией. Celery-задача гонит текст через GPT с помощью Instructor:
- Определение контекста — GPT анализирует транскрипцию: это задача, идея или решение?
- Извлечение элементов — для каждого типа своя Pydantic-схема. Задача получает title, description, priority, energy_level, deadline_hint. Идея — title, description, tags.
- Создание объектов — сохранение в базу со статусом
inboxиconfidence_score.
sequenceDiagram
participant P as Plaud Note
participant W as Webhook
participant C as Celery
participant G as GPT-4
participant DB as PostgreSQL
P->>W: POST /api/voice/webhook/
W->>C: process_voice_capture.delay()
C->>G: detect_context(transcript)
G-->>C: {type: "task", confidence: 0.92}
C->>G: extract_items(transcript, type)
G-->>C: [{title: "...", priority: "high"}]
C->>DB: Task.objects.create(status="inbox")
28 тестов покрывают весь бекенд. Моки на OpenAI — тесты не стоят денег и не зависят от доступности API.
Frontend: React + анимации + drag-and-drop
Дизайн-система
Тёмная тема по умолчанию. CSS custom properties для цветов, Framer Motion для микро-анимаций. Компоненты: Button, Badge, ConfidenceRing (SVG-кольцо для AI confidence), AudioWave (анимация волны), NeuralBreathing (пульсация загрузки).
Грабли с типами. HTMLMotionProps<"button"> из Framer Motion конфликтует с React по типу onDrag. У React это DragEventHandler, а у Framer Motion — свой handler. Пришлось описать интерфейс Button руками, без extends.
Ещё одна ловушка с Lucide React: тип LucideIcon из библиотеки несовместим с реальными компонентами иконок из-за size: string | number vs size: number. Решение — type IconComponent = typeof Inbox.
Triage Inbox
Ключевой экран. Сюда попадают все задачи из голосового пайплайна со статусом inbox. Навигация клавишами: J/K — вверх-вниз, 1 — accept (в todo), 2 — defer, 3 — reject. Batch actions для массовой обработки. Справа — детальная карточка с confidence score, транскрипцией и кнопками действий.
Kanban Board
Drag-and-drop через @dnd-kit. Колонки по статусам: Inbox, Todo, In Progress, Done. Перетащил карточку — PATCH-запрос обновляет статус. Карточки показывают приоритет цветом, дедлайн, и confidence ring от AI.
Command Palette
Cmd+K открывает палетку на базе cmdk. Поиск по задачам и проектам, быстрые действия. Fuzzy search, группировка по категориям. Вдохновлялся Linear и Raycast.
Состояние
Zustand для auth (токены, user info). TanStack Query для серверного состояния — кеширование, автоматическая инвалидация, optimistic updates при перетаскивании карточек в канбане.
graph TD
A[Zustand Auth Store] -->|JWT token| B[Axios Interceptor]
B -->|auto refresh| C[SimpleJWT]
D[TanStack Query] -->|fetch via| B
E[Inbox] -->|useInboxTasks| D
F[Task List] -->|useTaskList| D
G[Kanban] -->|useUpdateTask + optimistic| D
H[Command Palette] -->|useTaskList| D
Telegram-бот
Отдельный сервис в Docker Compose. python-telegram-bot 21.9, async handlers. Четыре команды: /today — задачи на сегодня, /inbox — непротриаженные, /task <текст> — создать задачу, /idea <текст> — записать идею. Привязка к пользователю по Telegram ID в профиле.
Инфраструктура
Docker Compose
6 сервисов: db (pgvector:pg16), redis (7-alpine), backend, celery, telegram, frontend. Healthcheck-и на базу и Redis, depends_on с condition: service_healthy.
SSL и nginx
Сертификат через certbot в Docker-контейнере с webroot-верификацией. Nginx проксирует: /api/ на Django, /ws/ на Daphne для WebSocket, всё остальное на Vite. Rate limiting 30 req/s, security headers, CSP с поддержкой WebSocket и blob.
Грабли с деплоем
Их было три, и все неочевидные.
EMFILE в Vite. Dev server в Docker падал с "too many open files". Два фикса: ulimits: nofile: 65536 в docker-compose и usePolling: true в vite config. Vite пытался использовать inotify, но в Docker с volume mount это работает плохо.
Белый экран (причина 1). Vite блокировал запросы с домена voxium.ru — "This host is not allowed". По умолчанию dev server принимает только localhost. Добавил allowedHosts: ['voxium.ru'].
Белый экран (причина 2). Nginx блокировал /node_modules/.vite/deps/react.js. Правило location ~ /\. было задумано для скрытия .env и .git, но зацепило и .vite. Добавил исключение в regex: (?!well-known|vite).
Результат
| Метрика | Значение |
|---|---|
| Время | ~2 часа от нуля до продакшена |
| Коммиты | 19 |
| Код | ~12,000 строк |
| Тесты | 28 (все зелёные) |
| Сервисы | 6 Docker-контейнеров |
| Экраны | Login, Inbox, Task List, Kanban, Command Palette |
| SSL | Let's Encrypt, валиден до мая 2026 |
pie title Время по этапам
"Backend: модели, API, voice" : 35
"Frontend: UI, страницы" : 35
"Инфраструктура: Docker, SSL" : 20
"Дебаг: типы, EMFILE, экран" : 10
Вся разработка — вайбкодинг с Claude Code в режиме subagent-driven development. План, подагент на каждую задачу, код-ревью, следующая задача. Подагенты работают с чистым контекстом — не путаются в предыдущем коде, не тащат ошибки из прошлых задач.
Следующий шаг — подключить реальный Plaud Note и прогнать живую запись через весь пайплайн. Посмотрим, как GPT справится с настоящей речью, а не тестовыми моками.