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

11 KiB
Raw Blame History

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. Ремоут уже настроен с токеном в URLgit 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).


Что было сделано в прошлой сессии

  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.