---
name: hermes-mini-project
description: |
  Полное состояние проекта Hermes Mini — Flutter-приложение AI-чата.
  Использовать при возврате к проекту для продолжения работы.
created: 2026-05-19
---

# Hermes Mini — состояние проекта

## Репозиторий
- **URL:** https://github.com/valldun1/hermes-mini
- **Владелец:** valldun1
- **Лицензия:** MIT
- **Публичный:** да

## Локальные файлы (Mac)
- **Проект:** `/Users/valen/Desktop/hermes-mini/`
- **APK:** `/Users/valen/Desktop/HermesMini.apk` (22 МБ, release, build #24 от 20.05.2026, дефолтный провайдер — Groq)

## Архитектура
- **Тип:** клиент-only Flutter-приложение (без сервера)
- **Подключение:** прямое HTTP streaming к любому OpenAI-совместимому API
- **Провайдеры:** OpenAI, OpenRouter, DeepSeek, Groq, свой сервер (Claude убран — Anthropic API несовместим с OpenAI format)
- **Хранение:** SharedPreferences (кэшированный инстанс)
- **UI:** Material 3, Markdown-рендер, тёмная/светлая тема

## Зависимости (pubspec.yaml)
```yaml
dependencies:
  flutter: sdk
  shared_preferences: ^2.3.3
  provider: ^6.1.2
  uuid: ^4.5.1
  flutter_markdown: ^0.7.3
  http: ^1.2.2
dev_dependencies:
  flutter_test: sdk
  flutter_lints: ^4.0.0
```
**Удалены:** `record`, `audioplayers`, `image_picker`, `permission_handler`, `web_socket_channel`, `intl`, `flutter_localizations`

## Структура файлов
```
hermes-mini/
├── README.md
├── LICENSE (MIT)
├── .gitignore
├── .github/workflows/build.yml
└── flutter_app/
    ├── pubspec.yaml
    ├── lib/
    │   ├── main.dart          (✓)
    │   ├── config/
    │   │   ├── settings.dart    (✓ _prefs cache, ThemeMode bounds check)
    │   │   └── theme.dart       (✓ Material 3, light/dark)
    │   ├── models/
    │   │   ├── chat_message.dart  (✓ text mutable)
    │   │   └── skill.dart         (✓ NEW — навыки: id, name, description, content, enabled)
    │   ├── screens/
    │   │   ├── chat_screen.dart     (✓ навыки инжектятся в system prompt)
    │   │   ├── settings_screen.dart (✓ убран пресет Claude)
    │   │   └── skills_screen.dart   (✓ NEW — список, создание, редактирование, ИИ-генератор)
    │   ├── services/
    │   │   ├── api_service.dart     (✓ исправлен: один Client, timeout, SSE parsing)
    │   │   └── skill_service.dart   (✓ NEW — CRUD навыков, дефолтные 4 навыка)
    │   └── widgets/
    │       └── message_bubble.dart  (✓ Markdown bubble)
    └── test/
        └── chat_message_test.dart
```

## CI/CD (GitHub Actions)
- **Workflow:** `.github/workflows/build.yml`
- **Flutter:** 3.24.5 (stable)
- **Ключевой фикс:** перед сборкой запускается `flutter create . --platforms=android` (проект создан вручную, не хватает Android/Gradle обвязки)
- **Секреты GitHub:** `GROQ_API_KEY` — встраивается в settings_screen.dart через sed на этапе сборки
- **Дефолтный провайдер:** Groq (⚡), модель `llama-3.3-70b-versatile`, Base URL `https://api.groq.com/openai/v1`
- **Артефакт:** `hermes-mini-apk` → `app-release.apk`

## PITFALLS — Dart/Flutter compile errors
- **`onTimeout` lambda type mismatch**: `http.Client.send().timeout(onTimeout: (_) { throw ... })` fails because `_` implies `dynamic` return, but timeout expects `StreamedResponse`. Fix: separate try/catch around the `.send()` call instead of throwing from onTimeout, OR use `onTimeout: () { ... }` (no param) if the return type matches.
- **`const JsonDecoder()` doesn't exist in Dart**: Use `jsonDecode(string)` from `dart:convert` instead. The `JsonDecoder` class exists but can't be used as `const` — `const JsonDecoder().convert(...)` causes compile error.
- **Always import `dart:convert`** when using `jsonDecode`, `jsonEncode`, `utf8`.

## История проблем и решений
1. ❌ Нет `git` на Mac (нужен Xcode CLT) → решено: push через GitHub REST API
2. ❌ Нет `gh` CLI → решено: curl + GitHub API напрямую
3. ❌ Flutter 3.19 слишком стар (Dart 3.3 < требуемого ^3.5.0) → обновлено до 3.24.5
4. ❌ Нет Android/Gradle структуры → `flutter create . --platforms=android` в CI
5. ❌ Плагин `record` ломал сборку Gradle → удалён из зависимостей
6. ❌ `onTimeout: (_) { throw ... }` — Never vs StreamedResponse → fix: try/catch outside .send()
7. ❌ `const JsonDecoder().convert(str)` — compile error → fix: `jsonDecode(str)`

## Исправленные баги (сессия 19.05.2026)
6. ✅ **Утечка http.Client** — переписан api_service.dart: единый `_client` экземпляр с `dispose()`
7. ✅ **Мёртвый код** — `_onWillPop()` удалён из chat_screen.dart
8. ✅ **Несовместимый Claude** — пресет Anthropic убран (API формат другой: /messages вместо /chat/completions, x-api-key вместо Bearer, anthropic-version обязательный)
9. ✅ **Таймауты** — добавлены 30с на стриминг, 15с на testConnection
10. ✅ **SSE парсинг** — исправлена обработка неполных строк при чанкинге
## История версий
- **v0.2.0** (2026-05-19) — система навыков (Skills), фикс API коннекта
- **v0.1.0** (2026-05-18) — первый билд, чат, настройки, стриминг

## Система навыков (Skills v1)
Навыки хранятся в SharedPreferences как JSON. 4 дефолтных: Краткость, Переводчик, Кодер, Мозговой штурм.
- Создание вручную: название + описание + инструкция для ИИ
- Создание через ИИ: пользователь описывает что хочет → LLM генерирует JSON {name, description, content}
- Активные навыки инжектятся в system prompt через `Skill.buildPrompt()`
- В чате показан бейдж с числом активных навыков
- Экран навыков: список, переключатели, редактирование, удаление

## Частые проблемы и решения
1. **Коннект к API не работает** — проверить:
   - API ключ в настройках (не пустой)
   - Base URL правильный (https://.../v1)
   - Модель существует у провайдера
   - Кнопка "Test Connection" в настройках — показывает ошибку
   - **Ошибка "No address associated with hostname" при работающем интернете** — на Android:
     - Проверить Настройки → Подключения → Private DNS (выключить или поставить `1.1.1.1`)
     - Причина: операторский DNS или Private DNS блокирует запросы, даже если интернет в целом работает
     - Решение: сменить DNS на телефоне или подключить VPN
2. **Сборка APK падает** — типичные ошибки:
   - `onTimeout: (_) =>` → должно быть `onTimeout: () =>` (Never return type)
   - `const JsonDecoder()` → использовать `jsonDecode()` из `dart:convert`
   - Missing import `dart:convert` для `jsonDecode`

## Что нужно доделать (TODO)
- [ ] **Иконка и splash** — сейчас дефолтная Flutter-иконка. Сделать брендированную (Hermes/Mini).
- [x] ~~Утечка клиента~~ — фиксировано: один `_client` с `dispose()`
- [x] ~~Мёртвый код~~ — `_onWillPop()` удалён
- [x] ~~Нерабочий пресет Claude~~ — убран (Anthropic API не OpenAI-совместимый)
- [ ] **Google Play / F-Droid** — подготовить подпись, fastlane.
- [ ] **История чатов** — сейчас только текущий чат, добавить список сессий.
- [ ] **Голосовой ввод** — если понадобится, вернуть через `speech_to_text`.
- [ ] **Датчики телефона** — модуль sensor_data для контекстного System Prompt:
      geolocator (GPS), battery_plus, connectivity_plus, sensors_plus (акселерометр/компас),
      light_sensor (освещённость), device_info_plus, torch_light (фонарик).
      LLM получает данные датчиков в system prompt и может адаптировать ответы.

## Как продолжить работу
1. Обновить файлы в `/Users/valen/Desktop/hermes-mini/`
2. Залить изменения на GitHub через REST API (PUT `/repos/valldun1/hermes-mini/contents/{path}`) — **нужен PAT с repo scope!**
3. Триггерить сборку: POST `/repos/valldun1/hermes-mini/actions/workflows/build.yml/dispatches` с `{"ref":"main"}`
4. Скачать APK из артефактов последнего run
5. Файл APK появится в `/tmp/apk_extract/` после unzip

## Блокеры
- ~~Нет GitHub PAT~~ —PAT получен от пользователя (7-дневный, май 2026). Нужно обновлять когда истечёт. Push через REST API работает.
