У меня есть 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:

  1. Определение контекста — GPT анализирует транскрипцию: это задача, идея или решение?
  2. Извлечение элементов — для каждого типа своя Pydantic-схема. Задача получает title, description, priority, energy_level, deadline_hint. Идея — title, description, tags.
  3. Создание объектов — сохранение в базу со статусом 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 справится с настоящей речью, а не тестовыми моками.