feat(voice): wake-word «Космо» в браузере (Шаг 3)
All checks were successful
Deploy / deploy (push) Successful in 6m33s
All checks were successful
Deploy / deploy (push) Successful in 6m33s
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.
This commit is contained in:
176
HANDOFF.md
Normal file
176
HANDOFF.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# 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 внутри.
|
||||
|
||||
---
|
||||
|
||||
## Как деплоить
|
||||
|
||||
```bash
|
||||
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 сам пересоберёт. Как проверить, что задеплоилось:
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
## Что было сделано в прошлой сессии
|
||||
|
||||
1. **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`.
|
||||
2. **Tap-targets подняты до 44px** — TopBar (HA-dot обёрнут в hit-zone), NotesTab (плюс/удалить), CalendarTab (close/nav/list), TimerHomeWidget.
|
||||
3. **Иконки/цвета** через токены вместо хардкода — TransportWidget (route.color = var(--data-*)), DeviceCard (DEVICE_ACCENT по типу), TopBar-сенсоры.
|
||||
4. **Свайп между табами** — добавлен в `app/page.tsx`, c bail-out по `[data-swipe-ignore]`.
|
||||
5. **Forecast day buttons / WeatherDayModal** — увеличены тапы и шрифты.
|
||||
6. **API `/api/countdowns`** — GET/POST/PUT/DELETE, бэкенд `/data/tablet-countdowns.json`. Дефолтная запись: Токио 2026-10-15.
|
||||
7. **FocusCard.tsx + CountdownCard.tsx** написаны, но **с Home убраны** по фидбеку: пользователю не понравилось, что виджет погоды стал меньше трамваев и пропали `feelsLike` / `humidity` / `WeatherAnimation`. Сейчас на Home — старый weather-hero с display-цифрой 76px, фоновой `WeatherAnimation size=180` (opacity 0.14), inline `WeatherAnimation 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 API
|
||||
- `a97dd11` — revert Home: вернул weather-hero с анимацией и деталями, убрал CountdownCard с главной
|
||||
|
||||
Оба задеплоены, прод 200 OK.
|
||||
Reference in New Issue
Block a user