openWakeWord pipeline на onnxruntime-web прямо на планшете. Цепочка: mic (16kHz, AudioWorklet) → melspectrogram.onnx → embedding_model.onnx (sliding 76-frame window, stride 8) → cosmo.onnx → score 0..1. Триггер при score≥0.5 → запускается тот же VAD-flow что и push-to-talk. - public/wake/ — cosmo.onnx (custom-trained на голос Даниила) + melspectrogram.onnx + embedding_model.onnx (~2.9MB вместе). - lib/wake-word.ts — WakeWordDetector class. ort грузится через <script src=/vad/ort.wasm.min.js> на клиенте — обход проблемы next-swc с парсингом import.meta.url в onnxruntime-web .mjs билдах. - VoiceController: тап = активация (нужен для AudioContext user-gesture), далее непрерывное слушание wake-word; на детект → MicVAD флоу. Долгий тап = выкл. Ручной тап остаётся как fallback. После деплоя Python-агент на .103 не нужен — можно архивировать home-voice-assistant. На .103 остаётся только ElevenLabs прокси :8888.
11 KiB
Smart Home Tablet — Handoff
Документ для следующей сессии. Где что лежит, как деплоить, что сделано.
Что это
Дашборд для планшета на стене: https://tablet.digital-home.site.
Next.js 14 App Router, TypeScript, standalone Docker build.
Контейнер живёт на сервере cosmo@192.168.31.60, образ собирается self-hosted Gitea Actions runner'ом и пушится через docker stop/rm/run (без compose для этого сервиса).
Где файлы
Локально (рабочая копия)
/tmp/tablet-work/smart-home-tablet/
Это git-clone из https://git.digital-home.site/daniil/smart-home-tablet. Ремоут уже настроен с токеном в URL — git push origin main работает без пароля.
На сервере (192.168.31.60)
/opt/digital-home/smart-home-tablet/ # код (что задеплоено)
/opt/digital-home/smart-home-tablet-data/ # JSON-стораджи (volume → /data в контейнере)
├─ tablet-notes.json
├─ tablet-timers.json
└─ tablet-countdowns.json
/opt/digital-home/tablet.env # секреты (HA_TOKEN, ELEVENLABS_*, VOICE_API_KEY, и т.д.)
Workflow деплоя
/opt/digital-home/smart-home-tablet/.gitea/workflows/deploy.yml
Триггер: push в main. Делает git pull → docker build → stop/rm/run. Контейнер: tablet-yfh53kixpwkjlo4zibglx4n2 на порту 3006, traefik роутит tablet.digital-home.site → 3000 внутри.
Как деплоить
cd /tmp/tablet-work/smart-home-tablet
# правки...
NODE_OPTIONS="--max-old-space-size=4096" npm run build # tsc на дефолтном heap падает OOM
git add -A && git commit -m "..." && git push origin main
Дальше Gitea Actions сам пересоберёт. Как проверить, что задеплоилось:
ssh -i ~/.ssh/id_ed25519 cosmo@192.168.31.60 \
"docker ps --filter name=tablet --format '{{.CreatedAt}}'"
curl -sk -o /dev/null -w "%{http_code} %{time_total}s\n" https://tablet.digital-home.site/
Свежий CreatedAt = новый билд поднялся.
SSH
ssh -i ~/.ssh/id_ed25519 cosmo@192.168.31.60
Ключ ~/.ssh/id_ed25519 авторизован. Не daniil@192.168.31.103 — туда с этой машины ключ не положен.
Голосовой ассистент
Tablet — только UI. Слой, отвечающий за wake-word/STT/LLM, живёт снаружи (на 192.168.31.103, плюс ElevenLabs через прокси 192.168.31.103:8888).
Поток событий:
Внешний Python-агент ──POST──▶ /api/voice/event (auth: VOICE_API_KEY)
│
▼
voiceBus (in-memory EventEmitter)
│
▼
Браузер ◀──SSE──── /api/voice/stream
│
▼
<VoiceOverlay />
Файлы голосового стека (в этом репо)
app/api/voice/
├─ event/route.ts # POST: принимает {event, agent, text} от Python-агента
├─ stream/route.ts # GET (SSE): отдаёт события в браузер
├─ tts/route.ts # POST: проксирует в ElevenLabs (cosmo / lusya голоса)
├─ timer/route.ts # голос-операции с таймерами
└─ tools/ # tool-calls от LLM (открыть таб, поставить таймер, ...)
lib/voice-bus.ts # EventEmitter, шарится между event и stream
lib/voice-tools.ts # описание tools для LLM
components/VoiceOverlay.tsx # Siri-style орб, рендерит wake/listening/command/response/error
Состояния (см. VoiceOverlay.tsx): idle | wake | listening | command | response | error. Два агента (cosmo / lusya) — каждому своя цветовая пара (фиолетовая / розовая) и свой ElevenLabs voice ID. Голоса в tablet.env: COSMO_TTS_VOICE, LUSYA_TTS_VOICE.
Агент сам шлёт idle когда закончил; в overlay есть safety-таймер 60с на случай падения агента.
Где сам Python-агент — на 192.168.31.103 (рабочая машина пользователя). Этот репо его не содержит и не деплоит.
Структура UI
app/
layout.tsx, page.tsx # Home: погода + комнаты + транспорт + ноуты + календарь
globals.css # дизайн-токены: --surface-*, --data-*, --space-*, утилиты .num/.grain/...
api/
auth/ ha/ weather/ transport/
notes/ calendar/ tasks/ savings/
countdowns/ voice/
components/
TopBar.tsx # часы, погода-чип, HA-статус (44px hit-zone), сенсор-чип
RoomTabs.tsx # переключатель Home/Зал/Кухня/Спальня
DeviceCard.tsx # лампы/тв/кондей/пуриф — accent по типу из DEVICE_ACCENT
TransportWidget.tsx # трамваи, цвета через --data-good/info/danger
WeatherAnimation.tsx # SVG-анимация по condition
NotesTab.tsx # стикеры (drag-to-trash, --data-* цвета)
CalendarTab.tsx # 3 модалки, у всех data-swipe-ignore
TimerHomeWidget.tsx, TimerModal.tsx, TimerWidget.tsx
Sidebar.tsx
VoiceOverlay.tsx # см. секцию выше
FocusCard.tsx # ⚠ написан, но НЕ используется на Home (см. ниже)
CountdownCard.tsx # ⚠ написан, но НЕ используется на Home
Свайп между табами
В app/page.tsx — handler на <main> через onPointerDown/Up, ref swipeStart. Условия срабатывания: |dx|>|dy|*1.6 && |dx|>90 && (dt<600 || |dx|>160). Bail-out если e.target.closest('[data-swipe-ignore]') — этот атрибут стоит на всех fixed-overlay (TimerModal, VoiceOverlay, модалки CalendarTab, weather day modal, draggable note).
Что было сделано в прошлой сессии
- CSS-рефакторинг —
app/globals.css: добавлены семантические токены--data-cool/info/good/warm/hot/danger/rose/violet/mood+ их-bgварианты черезcolor-mix(in srgb, ...)для тёмной и.lightтемы.--space-1..6,--touch-min: 44px,--touch-comfy: 56px. Утилиты:.num,.num-display(tabular-nums slashed-zero),.grain(SVG-noise через::after),.hit-zone,.eyebrow,.focus-card,.divider. - Tap-targets подняты до 44px — TopBar (HA-dot обёрнут в hit-zone), NotesTab (плюс/удалить), CalendarTab (close/nav/list), TimerHomeWidget.
- Иконки/цвета через токены вместо хардкода — TransportWidget (route.color = var(--data-*)), DeviceCard (DEVICE_ACCENT по типу), TopBar-сенсоры.
- Свайп между табами — добавлен в
app/page.tsx, c bail-out по[data-swipe-ignore]. - Forecast day buttons / WeatherDayModal — увеличены тапы и шрифты.
- API
/api/countdowns— GET/POST/PUT/DELETE, бэкенд/data/tablet-countdowns.json. Дефолтная запись: Токио 2026-10-15. - FocusCard.tsx + CountdownCard.tsx написаны, но с Home убраны по фидбеку: пользователю не понравилось, что виджет погоды стал меньше трамваев и пропали
feelsLike/humidity/WeatherAnimation. Сейчас на Home — старый weather-hero с display-цифрой 76px, фоновойWeatherAnimation size=180(opacity 0.14), inlineWeatherAnimation size=60, и тремя.eyebrow + .numблоками снизу (Ощущается/Влажность/Ветер).
Что НЕ делать без явной просьбы
- Не возвращать FocusCard на Home и не показывать CountdownCard на Home — пользователь явно отверг.
- Не делать виджеты Habits и Pulse — пользователь сказал "пока что их не добавляем".
Что осталось как orphan-файлы (есть, но не подключено)
components/FocusCard.tsx— context-engine с приоритетами bill-due / event-upcoming / tram-imminent / morning-outfit / countdown / night / quiet. Можно использовать в будущем, но Home не место.components/CountdownCard.tsx— ротация записей из/api/countdownsкаждые 8с.
Известные грабли
tsc --noEmitловит OOM на дефолтном heap — всегдаNODE_OPTIONS="--max-old-space-size=4096".lucide-react: нет экспортаTram, естьTramFront. Грепатьnode_modules/lucide-react/dist/lucide-react.d.tsесли сомневаешься.- Git push с локальной машины: ремоут уже c токеном, не сбрасывать (
git remote -vпокажетhttps://<token>@git.digital-home.site/...). Если потерялся — взять с сервера:ssh cosmo@192.168.31.60 'cat /opt/digital-home/smart-home-tablet/.git/config'. - Volume
/dataв контейнере — это/opt/digital-home/smart-home-tablet-data/на хосте. JSON-сторадж (notes/timers/countdowns) переживает редеплой только потому, что лежит там.
Последние коммиты сессии
e328055— большой CSS / tap-target / swipe / FocusCard / CountdownCard / countdowns APIa97dd11— revert Home: вернул weather-hero с анимацией и деталями, убрал CountdownCard с главной
Оба задеплоены, прод 200 OK.