Files
2026-06-16 09:26:31 +03:00

347 lines
15 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.
# Home AI Assistant
Домашний ИИ-ассистент с REST API, веб-интерфейсом и помидоро-таймером. LLM — OpenRouter (по умолчанию DeepSeek).
## Возможности
- Чат с потоковыми ответами (SSE), скриншоты и vision-разбор изображений
- Помидоро-таймер с циклом работа/перерывы, управление из чата (tool calling)
- Долгосрочная память, профиль пользователя, опциональный RAG (Qdrant)
- Фитнес: дневник, TDEE/Navy, графики веса и состава тела
- Списки покупок, календарь напоминаний
- Персонаж и генерация картинок (ComfyUI / RP Chat)
- Интеграции: Taiga, Gitea, погода, утренний дайджест, Netdata
- Мультипользовательская авторизация по API-токену (`/login`)
- Веб-интерфейс: Чат, Помидоро, Персонаж, Память, Фитнес, Покупки, Календарь, Настройки
- REST API для внешних клиентов — см. [Telegram-бот](telegram-bot/README.md)
## Быстрый старт
### 1. Настройка окружения
```bash
cp .env.example .env
```
Заполните в `.env`:
```env
OPENROUTER_API_KEY=sk-or-v1-...
BACKEND_PORT=8080
FRONTEND_PORT=3080
```
Если порт занят (например, 3000 уже используется Gitea), смените `FRONTEND_PORT` на свободный.
### 2. Запуск через Docker
```bash
docker compose up --build
```
- Backend API: http://localhost:${BACKEND_PORT:-8080}
- Web UI: http://localhost:${FRONTEND_PORT:-3080}
- Healthcheck: http://localhost:8080/api/v1/health
**Prod за nginx:** при загрузке скриншотов возможна ошибка `413 Request Entity Too Large` — дефолтный лимит nginx 1 MB. На **host nginx** (Ubuntu перед docker) добавьте `client_max_body_size 64m;` в `server { }` и в `location /api/`. Пример: [`deploy/nginx-host-assistant.conf.example`](deploy/nginx-host-assistant.conf.example). После правки: `sudo nginx -t && sudo systemctl reload nginx`. Контейнер frontend тоже поднимает лимит в `frontend/nginx.conf` — пересоберите образ.
Порты в `.env`:
| Переменная | По умолчанию | Назначение |
|------------|--------------|------------|
| `BACKEND_PORT` | 8080 | API с хоста |
| `FRONTEND_PORT` | 3080 | Веб-морда с хоста |
| `VITE_DEV_PORT` | 5173 | Frontend при `npm run dev` |
| `TAIGA_PORT` | 9000 | Taiga (фаза 2) |
| `GITEA_PORT` | 3000 | Gitea HTTP (фаза 2) |
| `GITEA_SSH_PORT` | 222 | Gitea SSH (фаза 2) |
| `QDRANT_PORT` | 6333 | Qdrant HTTP |
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | assistant | PostgreSQL в docker compose |
### 3. Локальная разработка
**Backend:**
```bash
cd backend
python -m venv .venv
.venv\Scripts\activate # Windows
pip install -r requirements-dev.txt
uvicorn app.main:app --reload --port 8080
```
Для локального backend без Docker задайте в `.env`:
```env
DATABASE_URL=sqlite:///./data/assistant.db
```
**Frontend:**
```bash
cd frontend
npm install
npm run dev
```
Vite dev-server: http://localhost:5173 (проксирует `/api` на backend).
## REST API
Полная схема — Swagger UI: `http://localhost:${BACKEND_PORT:-8080}/docs`
Основные эндпоинты (префикс `/api/v1`, авторизация `Authorization: Bearer <token>` если `AUTH_REQUIRED=true`):
| Method | Path | Описание |
|--------|------|----------|
| POST | `/login` | Получить сессию по API-токену |
| GET | `/health` | Healthcheck |
| POST/GET | `/chat/sessions` | Чат-сессии и история |
| POST | `/chat/sessions/{id}/messages` | Сообщение (SSE) |
| GET/POST | `/pomodoro/*` | Таймер |
| GET/PUT | `/memory`, `/profile` | Память и профиль |
| GET/POST | `/fitness/*` | Фитнес, графики `/fitness/charts` |
| GET/POST | `/shopping/*` | Списки покупок |
| GET/POST | `/reminders/*` | Напоминания и календарь |
| GET/POST | `/documents/*` | Загрузка документов (RAG) |
| GET | `/homelab/status`, `/homelab/weather` | Homelab |
| GET/PUT | `/settings` | RAG toggle, пользователи |
| GET/POST | `/projects`, `/work-items` | Taiga + Gitea |
| POST | `/webhooks/gitea` | Webhook автозакрытия |
## Taiga + Gitea (фаза 2)
Taiga и Gitea обычно работают **на хосте** (не в Docker compose). Контейнер backend достучится через `host.docker.internal` (настроено в `docker-compose.yml`). Публичные URL — в `.env` (`TAIGA_PUBLIC_URL`, `GITEA_PUBLIC_URL`).
### Настройка `.env`
```env
TAIGA_BASE_URL=http://host.docker.internal:9000
TAIGA_USERNAME=...
TAIGA_PASSWORD=...
TAIGA_PUBLIC_URL=https://taiga.example.com
GITEA_BASE_URL=http://host.docker.internal:3000
GITEA_TOKEN=... # Settings → Applications → Generate Token
GITEA_PUBLIC_URL=https://git.example.com
GITEA_WEBHOOK_SECRET=... # произвольная строка
```
### Первый запуск
```bash
# 1. Синхронизировать проекты Taiga (ID подтянутся автоматически)
curl -X POST http://localhost:8080/api/v1/projects/sync-taiga
# 2. Привязать Gitea repo к проекту Taiga
curl -X PUT http://localhost:8080/api/v1/projects/home-assistant/gitea \
-H "Content-Type: application/json" \
-d '{"gitea_owner":"Grigo","gitea_repo":"Home_assistant","default_branch":"main"}'
```
### Gitea webhook
В репозитории: **Settings → Webhooks → Add Webhook**:
- URL (выбери один вариант):
- **Рекомендуется:** `https://assistant.example.com/api/v1/webhooks/gitea` — nginx → `127.0.0.1:${BACKEND_PORT}`
- **Если Gitea в Docker:** `http://172.17.0.1:${BACKEND_PORT}/api/v1/webhooks/gitea` — не `127.0.0.1` (это localhost контейнера Gitea)
- Content type: `application/json`
- Secret: значение `GITEA_WEBHOOK_SECRET`
- Events: **Push**
Проверка из контейнера Gitea: `docker exec gitea wget -qO- http://172.17.0.1:8202/api/v1/health`
Test delivery в Gitea должен вернуть **200**, не **0**.
### Автозакрытие по коммиту
В сообщении коммита:
```
fix: кнопка сохранения
Closes gitea #12, taiga #45
```
Закроются Gitea issue #12 и Taiga story #45 (если только один ref — второй найдётся по связи в БД).
### Чат
«Заведи баг: кнопка не сохраняет настройки» → `create_work_item` → Taiga story + Gitea issue + ветка `feature/45-...`.
## Структура проекта
```
backend/ FastAPI, OpenRouter, PostgreSQL (docker) / SQLite (local dev)
frontend/ React + Vite
telegram-bot/ Telegram-клиент (отдельный VPS)
data/ uploads, generated media, SQLite-бэкап при миграции
deploy/ примеры nginx
```
## PostgreSQL
В `docker compose` по умолчанию поднимается **PostgreSQL 16**. Переменные — `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` в `.env`.
### Миграция с SQLite
Если уже есть `./data/assistant.db`:
```bash
# 1. Бэкап
cp -a data data.bak.$(date +%Y%m%d)
# 2. Поднять postgres и пересобрать backend (скрипт миграции в образе)
docker compose up -d postgres
docker compose build backend
# 3. Dry-run (подсчёт строк)
docker compose run --rm backend python scripts/migrate_sqlite_to_postgres.py --dry-run
# 4. Импорт (DATABASE_URL уже указывает на postgres в compose)
docker compose run --rm backend python scripts/migrate_sqlite_to_postgres.py
# 5. Перезапуск
docker compose up -d
```
Флаги: `--force` — очистить Postgres перед импортом; `--sqlite-path` — путь к файлу.
SQLite-файл **не удаляется** — остаётся бэкапом.
## Память и контекст
Долгосрочная память в БД (PostgreSQL или SQLite). При включённом RAG — семантический поиск фактов через Qdrant.
| Слой | Что хранит |
|------|------------|
| **Профиль** | имя, timezone, language, notes |
| **Факты** | устойчивые знания с категорией и важностью |
| **Сводка чата** | краткое содержание длинной сессии |
В system prompt на каждый ответ: персонаж → **время** → память → фитнес → **погода** → помидоро → проекты.
История чата обрезается до 40 последних сообщений; раннее — в `session_summaries`.
**Автоизвлечение:** после каждого ответа LLM анализирует ход диалога и сохраняет
устойчивые факты (`source=auto`). Отключить: `MEMORY_AUTO_EXTRACT=false`.
**UI:** вкладка `/memory` — профиль, факты, JSON-снимок для отладки.
### Tools
- `remember_fact` — «запомни, что…»
- `recall_memories` — поиск по памяти
- `forget_memory` — удалить факт по id
- `update_profile` — имя, часовой пояс и т.д.
- `update_session_summary` — сжать тему длинного чата
### API
| Method | Path | Описание |
|--------|------|----------|
| GET | `/api/v1/memory` | снимок памяти (+ `?session_id=`) |
| GET/PUT | `/api/v1/profile` | профиль |
| GET/POST | `/api/v1/memory/facts` | список / создать факт |
| DELETE | `/api/v1/memory/facts/{id}` | забыть |
| PUT | `/api/v1/memory/sessions/{id}/summary` | сводка чата |
## RAG (Qdrant)
Векторный поиск по фактам памяти и загруженным документам. **Два условия одновременно:**
1. `RAG_ENABLED=true` в `.env` (Qdrant должен быть доступен)
2. Toggle «RAG включён» в **Настройки** (веб-UI)
Загрузка документов: Settings → документы, или `POST /api/v1/documents/upload` (текст: `.txt`, `.md`, `.json`, `.csv`).
Tool в чате: `search_documents`.
Backfill существующих фактов:
```bash
docker compose exec backend python -m app.rag.migrate_memory_to_qdrant
```
Ограничения: нет API удаления документов; session summaries индексируются, но в чате читаются из SQLite; при ошибке embedding — fallback на топ фактов из БД.
## Фитнес-трекер
Профиль, дневник (еда/вода/вес/шаги/тренировки), калькуляторы TDEE и Navy (WHR/LBM/FFMI),
графики веса и состава тела (`/fitness`, API `/fitness/charts`), LLM-оценка ккал/БЖУ,
lookup wger + Open Food Facts, vision-импорт скриншотов Mi Fitness, напоминания в чат.
Чат: «обед: гречка 200г, курица 150г», «выпил 300 мл воды», «жим 80×5×3».
## Списки покупок
Несколько списков, позиции с количеством, отметка «куплено». Вкладка `/shopping`, tools в чате (`add_shopping_items`, `list_shopping_lists`, …).
Чат: «добавь молоко и хлеб в продукты», «что в списке покупок», «отметь молоко купленным».
## Homelab API (фаза 4)
Интеграции с домашней инфраструктурой:
| Сервис | URL по умолчанию | Назначение |
|--------|------------------|------------|
| Open-Meteo | `$OPENMETEO_BASE_URL` | Погода в контексте и tool `get_weather` |
| ComfyUI | `$COMFYUI_BASE_URL` | fallback / рофл-watcher |
| RP Chat (aiChatBot) | `http://host.docker.internal:8201` | `generate_image`: sd-prompt + Anima; appearance в `/character` |
| Netdata | `http://host.docker.internal:19999` | Алерты warning/critical → notice в чат |
**Утренний дайджест** (`MORNING_DIGEST_HOUR=8`): погода + RSS (Habr, r/programming по умолчанию).
По запросу: «что на улице», «будет ли дождь» → `get_weather`; полный брифинг → `get_morning_briefing`.
Переменные — в `.env.example` (секция Homelab).
### Проверка доступности
В образе backend нет `curl`/`wget`. Удобнее всего — API-диагностика (из контейнера или с хоста):
```bash
curl -s http://localhost:${BACKEND_PORT:-8202}/api/v1/homelab/status | python3 -m json.tool
```
Или изнутри backend через Python:
```bash
docker compose exec backend python -c "
import os, httpx
for url in [
os.environ.get('OPENMETEO_BASE_URL', 'http://host.docker.internal:8085').rstrip('/') + '/v1/forecast?latitude=59.93&longitude=30.33&current=temperature_2m',
os.environ.get('COMFYUI_BASE_URL', 'http://host.docker.internal:8188').rstrip('/') + '/system_stats',
'http://host.docker.internal:19999/api/v1/info',
]:
try:
r = httpx.get(url, timeout=10)
print(url, '->', r.status_code, r.text[:120])
except Exception as e:
print(url, '-> ERROR', e)
"
```
По умолчанию **Anima** (как в aiChatBot): `COMFYUI_UNET` + `COMFYUI_CLIP` + `COMFYUI_VAE` + style LoRA.
`COMFYUI_CHECKPOINT` оставь пустым. Для SD1.5/Pony — укажи checkpoint и очисти `COMFYUI_UNET`.
## Telegram-бот
Отдельный сервис в [`telegram-bot/`](telegram-bot/README.md): диалог с ассистентом с VPS, привязка API-токена, дублирование notice из чата.
Создание пользователя: Settings → Пользователи или `docker compose exec backend python -m scripts.create_user`.
## Разработка и тесты
```bash
cd backend
pip install -r requirements-dev.txt
pytest tests/ -q
```
Перед деплоем Jenkins запускает pytest (см. `Jenkinsfile`). CI на GitHub Actions нет — только Jenkins на linux-ноде.
## Следующие фазы
- Taiga/fitness в утреннем дайджесте
- LLM-мотивация в фитнес-напоминаниях (сейчас шаблонные строки)
- API удаления документов и re-index при включении RAG задним числом
## Модель
По умолчанию: `deepseek/deepseek-chat` через OpenRouter. Альтернатива для болтовни: `google/gemini-2.0-flash`.