Files
smart-home-tablet/HANDOFF.md
Cosmo 522d36d1a2
All checks were successful
Deploy / deploy (push) Successful in 6m33s
feat(voice): wake-word «Космо» в браузере (Шаг 3)
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.
2026-04-27 09:43:53 +00:00

177 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.