chore: Normalize line endings and update project documentation
- Normalize CRLF to LF across all source files - Replace CLAUDE.md.old with updated CLAUDE.md - Standardize configuration file formatting - Update module source files with consistent line endings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
439b55b176
commit
0dfb5f1535
148
.gitignore
vendored
148
.gitignore
vendored
@ -1,74 +1,74 @@
|
|||||||
# Build Outputs
|
# Build Outputs
|
||||||
bin/
|
bin/
|
||||||
obj/
|
obj/
|
||||||
*.exe
|
*.exe
|
||||||
*.dll
|
*.dll
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
# Visual Studio / Rider
|
# Visual Studio / Rider
|
||||||
.vs/
|
.vs/
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
*.user
|
*.user
|
||||||
*.suo
|
*.suo
|
||||||
*.cache
|
*.cache
|
||||||
*.docstates
|
*.docstates
|
||||||
*.tmp
|
*.tmp
|
||||||
|
|
||||||
# .NET Core
|
# .NET Core
|
||||||
project.lock.json
|
project.lock.json
|
||||||
project.fragment.lock.json
|
project.fragment.lock.json
|
||||||
artifacts/
|
artifacts/
|
||||||
|
|
||||||
# NuGet
|
# NuGet
|
||||||
*.nupkg
|
*.nupkg
|
||||||
*.snupkg
|
*.snupkg
|
||||||
packages/
|
packages/
|
||||||
!packages/build/
|
!packages/build/
|
||||||
|
|
||||||
# Test Results
|
# Test Results
|
||||||
TestResults/
|
TestResults/
|
||||||
[Tt]est[Rr]esult*/
|
[Tt]est[Rr]esult*/
|
||||||
*.trx
|
*.trx
|
||||||
*.coverage
|
*.coverage
|
||||||
*.coveragexml
|
*.coveragexml
|
||||||
|
|
||||||
# Runtime Data & Logs
|
# Runtime Data & Logs
|
||||||
data/
|
data/
|
||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
*.csv
|
*.csv
|
||||||
|
|
||||||
# Configuration Secrets
|
# Configuration Secrets
|
||||||
appsettings.Development.json
|
appsettings.Development.json
|
||||||
appsettings.Production.json
|
appsettings.Production.json
|
||||||
config/secrets.json
|
config/secrets.json
|
||||||
*.key
|
*.key
|
||||||
|
|
||||||
# Audio Models & Cache
|
# Audio Models & Cache
|
||||||
whisper-models/
|
whisper-models/
|
||||||
*.bin
|
*.bin
|
||||||
audio-cache/
|
audio-cache/
|
||||||
|
|
||||||
# OS Generated
|
# OS Generated
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
|
|
||||||
# Large Media Files
|
# Large Media Files
|
||||||
*.mp4
|
*.mp4
|
||||||
*.avi
|
*.avi
|
||||||
*.mov
|
*.mov
|
||||||
*.wmv
|
*.wmv
|
||||||
*.mp3
|
*.mp3
|
||||||
*.wavbuild/
|
*.wavbuild/
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
*.so
|
*.so
|
||||||
*.dll
|
*.dll
|
||||||
build/
|
build/
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
*.so
|
*.so
|
||||||
*.dll
|
*.dll
|
||||||
|
|||||||
64
CLAUDE.md
Normal file
64
CLAUDE.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# AISSIA - Assistant Personnel Intelligent
|
||||||
|
|
||||||
|
Assistant pour gérer le temps, l'hyperfocus et l'apprentissage de langues. Basé sur **GroveEngine** (C++17 hot-reload module system).
|
||||||
|
|
||||||
|
## Statut
|
||||||
|
|
||||||
|
| Module | Status | Description |
|
||||||
|
|--------|--------|-------------|
|
||||||
|
| SchedulerModule | Fait | Détection hyperfocus, rappels pauses |
|
||||||
|
| NotificationModule | Fait | Alertes système, TTS, priorités |
|
||||||
|
| AIAssistantModule | TODO | Intégration LLM (Claude API) |
|
||||||
|
| LanguageLearningModule | TODO | Pratique langue cible |
|
||||||
|
| DataModule | TODO | SQLite persistence |
|
||||||
|
|
||||||
|
## Règles de Développement
|
||||||
|
|
||||||
|
### Contraintes Modules
|
||||||
|
- **200-300 lignes max** par module
|
||||||
|
- **Logique métier pure** (pas de threading/network dans les modules)
|
||||||
|
- **Communication JSON** via IIO pub/sub
|
||||||
|
- **Hot-reload ready** : sérialiser tout l'état dans `getState()`
|
||||||
|
|
||||||
|
### NEVER
|
||||||
|
- `cmake ..` ou `#include "../"` (dépendances parent)
|
||||||
|
- Modules > 300 lignes
|
||||||
|
- Infrastructure dans les modules
|
||||||
|
|
||||||
|
### ALWAYS
|
||||||
|
- Build autonome depuis le module
|
||||||
|
- JSON pour toute communication inter-modules
|
||||||
|
- Topics pub/sub : `module:event` (ex: `scheduler:hyperfocus_detected`)
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
Aissia/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.cpp # Main loop 10Hz + hot-reload
|
||||||
|
│ └── modules/ # Modules implémentés
|
||||||
|
│ ├── SchedulerModule.*
|
||||||
|
│ └── NotificationModule.*
|
||||||
|
├── config/ # JSON config par module
|
||||||
|
├── external/GroveEngine/ # Engine (symlink)
|
||||||
|
└── docs/ # Documentation détaillée
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -B build && cmake --build build -j4
|
||||||
|
./build/aissia
|
||||||
|
|
||||||
|
# Hot-reload: rebuild modules seulement
|
||||||
|
cmake --build build --target modules
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
| Doc | Contenu |
|
||||||
|
|-----|---------|
|
||||||
|
| `docs/GROVEENGINE_GUIDE.md` | API complète IModule, IIO, IDataNode, hot-reload |
|
||||||
|
| `docs/project-overview.md` | Architecture AISSIA, phases dev |
|
||||||
|
| `docs/architecture/intelligent-document-retrieval.md` | AIAssistantModule: retrieval agentique, multi-provider LLM |
|
||||||
|
| `README.md` | Quick start, roadmap |
|
||||||
112
CLAUDE.md.old
112
CLAUDE.md.old
@ -1,112 +0,0 @@
|
|||||||
# AISSIA - Assistant Personnel Intelligent
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Assistant personnel qui aide à gérer le temps, l'hyperfocus et l'apprentissage de langues. Interventions proactives avec l'IA pour forcer les transitions et planifier intelligemment.
|
|
||||||
|
|
||||||
## Fonctionnalités MVP
|
|
||||||
|
|
||||||
1. **SchedulerModule** : Planning de tâches, détection hyperfocus (max 2h), rappels de pauses
|
|
||||||
2. **AIAssistantModule** : Interventions contextuelles via Claude, dialogue naturel
|
|
||||||
3. **LanguageLearningModule** : Conversation dans langue cible, corrections intelligentes
|
|
||||||
4. **NotificationModule** : Alertes système Windows, TTS, support multilingue
|
|
||||||
5. **DataModule** : SQLite local, historique, métriques
|
|
||||||
|
|
||||||
## Architecture Technique
|
|
||||||
|
|
||||||
### Principe : Architecture Modulaire WarFactory
|
|
||||||
|
|
||||||
Hot-reload 0.4ms, modules 200-300 lignes, build autonome par module.
|
|
||||||
|
|
||||||
```
|
|
||||||
MainServer Process
|
|
||||||
├── CoordinationModule → Charge appconfig.json
|
|
||||||
├── DebugEngine → SequentialModuleSystem
|
|
||||||
└── Modules (.dll)
|
|
||||||
├── scheduler.dll
|
|
||||||
├── ai-assistant.dll
|
|
||||||
├── language.dll
|
|
||||||
├── notification.dll
|
|
||||||
└── data.dll
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5 Interfaces Fondamentales
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
ICoordinationModule → Orchestrateur global
|
|
||||||
IEngine → DebugEngine → HighPerfEngine
|
|
||||||
IModuleSystem → Sequential → Threaded → Cluster
|
|
||||||
IModule → Logique métier pure (200 lignes max)
|
|
||||||
IIO → IntraIO → LocalIO → NetworkIO
|
|
||||||
```
|
|
||||||
|
|
||||||
### Contraintes Critiques
|
|
||||||
|
|
||||||
**Modules** :
|
|
||||||
- 200-300 lignes maximum
|
|
||||||
- Logique métier pure (pas de threading, network)
|
|
||||||
- Communication JSON uniquement
|
|
||||||
- Build autonome : `cmake .` depuis module
|
|
||||||
|
|
||||||
**NEVER** :
|
|
||||||
- ❌ `cmake ..` ou `#include "../"`
|
|
||||||
- ❌ Modules > 300 lignes
|
|
||||||
- ❌ Dépendances entre modules
|
|
||||||
|
|
||||||
**ALWAYS** :
|
|
||||||
- ✅ Build autonome
|
|
||||||
- ✅ JSON communication
|
|
||||||
- ✅ Hot-reload ready
|
|
||||||
- ✅ Task-centric design
|
|
||||||
|
|
||||||
### Workflow Développement
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd modules/scheduler/
|
|
||||||
cmake . # NEVER cmake ..
|
|
||||||
make
|
|
||||||
./scheduler-test
|
|
||||||
|
|
||||||
# Edit SchedulerModule.cpp → Save → Hot-reload 0.4ms
|
|
||||||
```
|
|
||||||
|
|
||||||
### Communication Inter-Modules (JSON)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"event": "hyperfocus", "duration_minutes": 120}
|
|
||||||
→ AIAssistantModule
|
|
||||||
{"type": "break_suggestion", "message": "Pause ?"}
|
|
||||||
→ NotificationModule
|
|
||||||
{"notification": "system_toast", "tts": true}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Structure Projet
|
|
||||||
|
|
||||||
```
|
|
||||||
Aissia/
|
|
||||||
├── CLAUDE.md
|
|
||||||
├── docs/ # Documentation détaillée
|
|
||||||
├── modules/ # À créer
|
|
||||||
│ ├── scheduler/
|
|
||||||
│ ├── ai-assistant/
|
|
||||||
│ ├── language-learning/
|
|
||||||
│ ├── notification/
|
|
||||||
│ └── data/
|
|
||||||
└── src/ # Infrastructure
|
|
||||||
```
|
|
||||||
|
|
||||||
## Priorités
|
|
||||||
|
|
||||||
1. Infrastructure (IModule, IEngine, hot-reload)
|
|
||||||
2. SchedulerModule
|
|
||||||
3. NotificationModule
|
|
||||||
4. AIAssistantModule
|
|
||||||
5. LanguageLearningModule
|
|
||||||
6. DataModule
|
|
||||||
|
|
||||||
## Références
|
|
||||||
|
|
||||||
- `docs/README.md` : Vue d'ensemble
|
|
||||||
- `docs/architecture/architecture-technique.md` : Architecture complète
|
|
||||||
- `CDCDraft.md` : Cahier des charges
|
|
||||||
- GroveEngine : Architecture source WarFactory (accès via `.claude/settings.json`)
|
|
||||||
178
CMakeLists.txt
178
CMakeLists.txt
@ -1,89 +1,89 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(Aissia VERSION 0.1.0 LANGUAGES CXX)
|
project(Aissia VERSION 0.1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
# Export compile commands for IDE support
|
# Export compile commands for IDE support
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# GroveEngine Integration
|
# GroveEngine Integration
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
set(GROVE_BUILD_TESTS OFF CACHE BOOL "Disable GroveEngine tests" FORCE)
|
set(GROVE_BUILD_TESTS OFF CACHE BOOL "Disable GroveEngine tests" FORCE)
|
||||||
add_subdirectory(external/GroveEngine)
|
add_subdirectory(external/GroveEngine)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Main Executable
|
# Main Executable
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
add_executable(aissia
|
add_executable(aissia
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(aissia PRIVATE
|
target_link_libraries(aissia PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(aissia PRIVATE
|
target_include_directories(aissia PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
)
|
)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Hot-Reloadable Modules (.so)
|
# Hot-Reloadable Modules (.so)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# SchedulerModule - Gestion du temps et détection hyperfocus
|
# SchedulerModule - Gestion du temps et détection hyperfocus
|
||||||
add_library(SchedulerModule SHARED
|
add_library(SchedulerModule SHARED
|
||||||
src/modules/SchedulerModule.cpp
|
src/modules/SchedulerModule.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(SchedulerModule PRIVATE
|
target_link_libraries(SchedulerModule PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
)
|
)
|
||||||
set_target_properties(SchedulerModule PROPERTIES
|
set_target_properties(SchedulerModule PROPERTIES
|
||||||
PREFIX "lib"
|
PREFIX "lib"
|
||||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/modules
|
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/modules
|
||||||
)
|
)
|
||||||
|
|
||||||
# NotificationModule - Alertes système et TTS
|
# NotificationModule - Alertes système et TTS
|
||||||
add_library(NotificationModule SHARED
|
add_library(NotificationModule SHARED
|
||||||
src/modules/NotificationModule.cpp
|
src/modules/NotificationModule.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(NotificationModule PRIVATE
|
target_link_libraries(NotificationModule PRIVATE
|
||||||
GroveEngine::impl
|
GroveEngine::impl
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
)
|
)
|
||||||
set_target_properties(NotificationModule PROPERTIES
|
set_target_properties(NotificationModule PROPERTIES
|
||||||
PREFIX "lib"
|
PREFIX "lib"
|
||||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/modules
|
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/modules
|
||||||
)
|
)
|
||||||
|
|
||||||
# Futurs modules (décommenter quand implémentés):
|
# Futurs modules (décommenter quand implémentés):
|
||||||
# add_library(AIAssistantModule SHARED src/modules/AIAssistantModule.cpp)
|
# add_library(AIAssistantModule SHARED src/modules/AIAssistantModule.cpp)
|
||||||
# add_library(LanguageLearningModule SHARED src/modules/LanguageLearningModule.cpp)
|
# add_library(LanguageLearningModule SHARED src/modules/LanguageLearningModule.cpp)
|
||||||
# add_library(DataModule SHARED src/modules/DataModule.cpp)
|
# add_library(DataModule SHARED src/modules/DataModule.cpp)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Copy config files to build directory
|
# Copy config files to build directory
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/config/
|
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/config/
|
||||||
DESTINATION ${CMAKE_BINARY_DIR}/config)
|
DESTINATION ${CMAKE_BINARY_DIR}/config)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Development targets
|
# Development targets
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Quick rebuild of modules only (for hot-reload workflow)
|
# Quick rebuild of modules only (for hot-reload workflow)
|
||||||
add_custom_target(modules
|
add_custom_target(modules
|
||||||
DEPENDS SchedulerModule NotificationModule
|
DEPENDS SchedulerModule NotificationModule
|
||||||
COMMENT "Building hot-reloadable modules only"
|
COMMENT "Building hot-reloadable modules only"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run Aissia
|
# Run Aissia
|
||||||
add_custom_target(run
|
add_custom_target(run
|
||||||
COMMAND $<TARGET_FILE:aissia>
|
COMMAND $<TARGET_FILE:aissia>
|
||||||
DEPENDS aissia modules
|
DEPENDS aissia modules
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
COMMENT "Running Aissia"
|
COMMENT "Running Aissia"
|
||||||
)
|
)
|
||||||
|
|||||||
444
README.md
444
README.md
@ -1,222 +1,222 @@
|
|||||||
# AISSIA - AI Smart Schedule & Interactive Assistant
|
# AISSIA - AI Smart Schedule & Interactive Assistant
|
||||||
|
|
||||||
**AISSIA** is an intelligent personal assistant for time management, hyperfocus detection, and language learning, powered by **GroveEngine**.
|
**AISSIA** is an intelligent personal assistant for time management, hyperfocus detection, and language learning, powered by **GroveEngine**.
|
||||||
|
|
||||||
## What is AISSIA?
|
## What is AISSIA?
|
||||||
|
|
||||||
AISSIA helps manage:
|
AISSIA helps manage:
|
||||||
- **Hyperfocus**: Detects when you've been working too long and need a break
|
- **Hyperfocus**: Detects when you've been working too long and need a break
|
||||||
- **Time Management**: Intelligent scheduling and task planning
|
- **Time Management**: Intelligent scheduling and task planning
|
||||||
- **Language Learning**: Interactive practice in target languages
|
- **Language Learning**: Interactive practice in target languages
|
||||||
- **Notifications**: Context-aware alerts with TTS support
|
- **Notifications**: Context-aware alerts with TTS support
|
||||||
|
|
||||||
## Built on GroveEngine
|
## Built on GroveEngine
|
||||||
|
|
||||||
AISSIA leverages **GroveEngine**, a C++17 hot-reload module system that enables:
|
AISSIA leverages **GroveEngine**, a C++17 hot-reload module system that enables:
|
||||||
|
|
||||||
- **Hot-Reload**: Modify code at runtime without losing state (<1ms reload latency)
|
- **Hot-Reload**: Modify code at runtime without losing state (<1ms reload latency)
|
||||||
- **Modular Architecture**: Self-contained modules (200-300 lines each)
|
- **Modular Architecture**: Self-contained modules (200-300 lines each)
|
||||||
- **Pub/Sub Communication**: Decoupled inter-module messaging
|
- **Pub/Sub Communication**: Decoupled inter-module messaging
|
||||||
- **State Preservation**: Automatic state serialization across reloads
|
- **State Preservation**: Automatic state serialization across reloads
|
||||||
- **Configuration Hot-Reload**: Update settings without code changes
|
- **Configuration Hot-Reload**: Update settings without code changes
|
||||||
|
|
||||||
### Why GroveEngine?
|
### Why GroveEngine?
|
||||||
|
|
||||||
✅ **Ultra-fast Development**: Hot-reload validated at 0.4ms
|
✅ **Ultra-fast Development**: Hot-reload validated at 0.4ms
|
||||||
✅ **Type Safety**: Strong C++ typing, no "wildcode"
|
✅ **Type Safety**: Strong C++ typing, no "wildcode"
|
||||||
✅ **Proven Architecture**: Production-ready module system
|
✅ **Proven Architecture**: Production-ready module system
|
||||||
✅ **Privacy-First**: Local mode, data never uploaded
|
✅ **Privacy-First**: Local mode, data never uploaded
|
||||||
✅ **Progressive Evolution**: MVP → Production → Cloud without rewrite
|
✅ **Progressive Evolution**: MVP → Production → Cloud without rewrite
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
Aissia/
|
Aissia/
|
||||||
├── external/
|
├── external/
|
||||||
│ └── GroveEngine/ # GroveEngine (symlink)
|
│ └── GroveEngine/ # GroveEngine (symlink)
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── main.cpp # Main application loop
|
│ ├── main.cpp # Main application loop
|
||||||
│ └── modules/
|
│ └── modules/
|
||||||
│ ├── SchedulerModule.* # Time management & hyperfocus
|
│ ├── SchedulerModule.* # Time management & hyperfocus
|
||||||
│ ├── NotificationModule.* # Alerts & TTS
|
│ ├── NotificationModule.* # Alerts & TTS
|
||||||
│ ├── AIAssistantModule.* # (TODO) LLM integration
|
│ ├── AIAssistantModule.* # (TODO) LLM integration
|
||||||
│ ├── LanguageLearningModule.* # (TODO) Language practice
|
│ ├── LanguageLearningModule.* # (TODO) Language practice
|
||||||
│ └── DataModule.* # (TODO) SQLite persistence
|
│ └── DataModule.* # (TODO) SQLite persistence
|
||||||
├── config/
|
├── config/
|
||||||
│ ├── scheduler.json
|
│ ├── scheduler.json
|
||||||
│ └── notification.json
|
│ └── notification.json
|
||||||
├── docs/
|
├── docs/
|
||||||
│ ├── README.md # Project documentation
|
│ ├── README.md # Project documentation
|
||||||
│ └── GROVEENGINE_GUIDE.md # GroveEngine user guide
|
│ └── GROVEENGINE_GUIDE.md # GroveEngine user guide
|
||||||
└── CMakeLists.txt
|
└── CMakeLists.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
### Implemented
|
### Implemented
|
||||||
|
|
||||||
**SchedulerModule**: Time management and hyperfocus detection
|
**SchedulerModule**: Time management and hyperfocus detection
|
||||||
- Tracks work sessions and break intervals
|
- Tracks work sessions and break intervals
|
||||||
- Detects hyperfocus based on configurable threshold (default: 120 minutes)
|
- Detects hyperfocus based on configurable threshold (default: 120 minutes)
|
||||||
- Reminds for breaks (default: every 45 minutes)
|
- Reminds for breaks (default: every 45 minutes)
|
||||||
- Task estimation and tracking
|
- Task estimation and tracking
|
||||||
|
|
||||||
**NotificationModule**: System alerts with priority levels
|
**NotificationModule**: System alerts with priority levels
|
||||||
- Priority-based notifications (LOW, NORMAL, HIGH, URGENT)
|
- Priority-based notifications (LOW, NORMAL, HIGH, URGENT)
|
||||||
- Silent mode (respects URGENT priority)
|
- Silent mode (respects URGENT priority)
|
||||||
- TTS support (configurable)
|
- TTS support (configurable)
|
||||||
- Multilingual support (fr/en/jp)
|
- Multilingual support (fr/en/jp)
|
||||||
- Queue management with rate limiting
|
- Queue management with rate limiting
|
||||||
|
|
||||||
### Planned
|
### Planned
|
||||||
|
|
||||||
**AIAssistantModule**: LLM-powered contextual interventions
|
**AIAssistantModule**: LLM-powered contextual interventions
|
||||||
- Claude API integration
|
- Claude API integration
|
||||||
- Context-aware suggestions
|
- Context-aware suggestions
|
||||||
- Intelligent document retrieval
|
- Intelligent document retrieval
|
||||||
|
|
||||||
**LanguageLearningModule**: Language practice and learning
|
**LanguageLearningModule**: Language practice and learning
|
||||||
- Conversation in target language
|
- Conversation in target language
|
||||||
- Vocabulary tracking
|
- Vocabulary tracking
|
||||||
- Progress monitoring
|
- Progress monitoring
|
||||||
|
|
||||||
**DataModule**: SQLite persistence
|
**DataModule**: SQLite persistence
|
||||||
- Task history
|
- Task history
|
||||||
- Session analytics
|
- Session analytics
|
||||||
- Configuration backup
|
- Configuration backup
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- CMake 3.20+
|
- CMake 3.20+
|
||||||
- C++17 compiler (GCC/Clang)
|
- C++17 compiler (GCC/Clang)
|
||||||
- GroveEngine (included via symlink)
|
- GroveEngine (included via symlink)
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Configure
|
# Configure
|
||||||
cmake -B build
|
cmake -B build
|
||||||
|
|
||||||
# Build everything
|
# Build everything
|
||||||
cmake --build build -j4
|
cmake --build build -j4
|
||||||
|
|
||||||
# Build modules only (for hot-reload workflow)
|
# Build modules only (for hot-reload workflow)
|
||||||
cmake --build build --target modules
|
cmake --build build --target modules
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
./build/aissia
|
./build/aissia
|
||||||
```
|
```
|
||||||
|
|
||||||
### Hot-Reload Workflow
|
### Hot-Reload Workflow
|
||||||
|
|
||||||
1. Start AISSIA: `./build/aissia`
|
1. Start AISSIA: `./build/aissia`
|
||||||
2. Edit a module: `src/modules/SchedulerModule.cpp`
|
2. Edit a module: `src/modules/SchedulerModule.cpp`
|
||||||
3. Rebuild: `cmake --build build --target modules`
|
3. Rebuild: `cmake --build build --target modules`
|
||||||
4. **Module reloads automatically with state preserved**
|
4. **Module reloads automatically with state preserved**
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Configuration files in `config/`:
|
Configuration files in `config/`:
|
||||||
|
|
||||||
**scheduler.json**:
|
**scheduler.json**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"hyperfocusThresholdMinutes": 120,
|
"hyperfocusThresholdMinutes": 120,
|
||||||
"breakReminderIntervalMinutes": 45,
|
"breakReminderIntervalMinutes": 45,
|
||||||
"breakDurationMinutes": 10,
|
"breakDurationMinutes": 10,
|
||||||
"workdayStartHour": 9,
|
"workdayStartHour": 9,
|
||||||
"workdayEndHour": 18
|
"workdayEndHour": 18
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**notification.json**:
|
**notification.json**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"language": "fr",
|
"language": "fr",
|
||||||
"silentMode": false,
|
"silentMode": false,
|
||||||
"ttsEnabled": false,
|
"ttsEnabled": false,
|
||||||
"maxQueueSize": 50
|
"maxQueueSize": 50
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- **[docs/README.md](docs/README.md)**: Project documentation and architecture
|
- **[docs/README.md](docs/README.md)**: Project documentation and architecture
|
||||||
- **[docs/GROVEENGINE_GUIDE.md](docs/GROVEENGINE_GUIDE.md)**: Complete GroveEngine user guide
|
- **[docs/GROVEENGINE_GUIDE.md](docs/GROVEENGINE_GUIDE.md)**: Complete GroveEngine user guide
|
||||||
- **[CDCDraft.md](CDCDraft.md)**: Detailed requirements specification (French)
|
- **[CDCDraft.md](CDCDraft.md)**: Detailed requirements specification (French)
|
||||||
|
|
||||||
## Development Philosophy
|
## Development Philosophy
|
||||||
|
|
||||||
### MVP-First
|
### MVP-First
|
||||||
|
|
||||||
- **Phase 1 (Required)**: Local Windows mode only
|
- **Phase 1 (Required)**: Local Windows mode only
|
||||||
- **Phase 2+ (Optional)**: Cloud features if needed
|
- **Phase 2+ (Optional)**: Cloud features if needed
|
||||||
- **Configuration-Driven**: 90% of needs via JSON
|
- **Configuration-Driven**: 90% of needs via JSON
|
||||||
- **Simplicity First**: Complexity emerges from interaction
|
- **Simplicity First**: Complexity emerges from interaction
|
||||||
|
|
||||||
### Module Development
|
### Module Development
|
||||||
|
|
||||||
Each module is a self-contained unit (~200-300 lines):
|
Each module is a self-contained unit (~200-300 lines):
|
||||||
|
|
||||||
1. Implements `IModule` interface
|
1. Implements `IModule` interface
|
||||||
2. Pure business logic (no infrastructure code)
|
2. Pure business logic (no infrastructure code)
|
||||||
3. Communicates via `IIO` pub/sub
|
3. Communicates via `IIO` pub/sub
|
||||||
4. Serializes state for hot-reload
|
4. Serializes state for hot-reload
|
||||||
5. Configurable via `IDataNode`
|
5. Configurable via `IDataNode`
|
||||||
|
|
||||||
See `docs/GROVEENGINE_GUIDE.md` for detailed module development guide.
|
See `docs/GROVEENGINE_GUIDE.md` for detailed module development guide.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
AISSIA runs on a simple loop (10Hz for assistant workload):
|
AISSIA runs on a simple loop (10Hz for assistant workload):
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────┐
|
||||||
│ Main Loop (10Hz) │
|
│ Main Loop (10Hz) │
|
||||||
│ │
|
│ │
|
||||||
│ ┌────────────────────────────────────┐ │
|
│ ┌────────────────────────────────────┐ │
|
||||||
│ │ 1. Check for module file changes │ │
|
│ │ 1. Check for module file changes │ │
|
||||||
│ │ 2. Hot-reload if modified │ │
|
│ │ 2. Hot-reload if modified │ │
|
||||||
│ │ 3. Process all modules │ │
|
│ │ 3. Process all modules │ │
|
||||||
│ │ 4. Route inter-module messages │ │
|
│ │ 4. Route inter-module messages │ │
|
||||||
│ │ 5. Sleep to maintain 10Hz │ │
|
│ │ 5. Sleep to maintain 10Hz │ │
|
||||||
│ └────────────────────────────────────┘ │
|
│ └────────────────────────────────────┘ │
|
||||||
└─────────────────────────────────────────────┘
|
└─────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
Modules communicate via topics:
|
Modules communicate via topics:
|
||||||
- `scheduler:hyperfocus_detected` → NotificationModule alerts user
|
- `scheduler:hyperfocus_detected` → NotificationModule alerts user
|
||||||
- `notification:alert_sent` → DataModule logs event
|
- `notification:alert_sent` → DataModule logs event
|
||||||
- `aiassistant:suggestion` → NotificationModule displays suggestion
|
- `aiassistant:suggestion` → NotificationModule displays suggestion
|
||||||
|
|
||||||
## Technical Stack
|
## Technical Stack
|
||||||
|
|
||||||
- **Core**: C++17
|
- **Core**: C++17
|
||||||
- **Module System**: GroveEngine
|
- **Module System**: GroveEngine
|
||||||
- **Logging**: spdlog
|
- **Logging**: spdlog
|
||||||
- **Data Format**: JSON (nlohmann/json)
|
- **Data Format**: JSON (nlohmann/json)
|
||||||
- **Build**: CMake 3.20+
|
- **Build**: CMake 3.20+
|
||||||
- **Future**: SQLite (DataModule), Claude API (AIAssistantModule)
|
- **Future**: SQLite (DataModule), Claude API (AIAssistantModule)
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [x] Project setup with GroveEngine
|
- [x] Project setup with GroveEngine
|
||||||
- [x] SchedulerModule (time management & hyperfocus)
|
- [x] SchedulerModule (time management & hyperfocus)
|
||||||
- [x] NotificationModule (alerts & TTS)
|
- [x] NotificationModule (alerts & TTS)
|
||||||
- [ ] AIAssistantModule (LLM integration)
|
- [ ] AIAssistantModule (LLM integration)
|
||||||
- [ ] LanguageLearningModule (language practice)
|
- [ ] LanguageLearningModule (language practice)
|
||||||
- [ ] DataModule (SQLite persistence)
|
- [ ] DataModule (SQLite persistence)
|
||||||
- [ ] Windows Toast notifications
|
- [ ] Windows Toast notifications
|
||||||
- [ ] Real TTS integration
|
- [ ] Real TTS integration
|
||||||
- [ ] Claude API integration
|
- [ ] Claude API integration
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
To be determined
|
To be determined
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- **GroveEngine**: [../GroveEngine](../GroveEngine)
|
- **GroveEngine**: [../GroveEngine](../GroveEngine)
|
||||||
- **Claude Code**: https://docs.claude.com/en/docs/claude-code
|
- **Claude Code**: https://docs.claude.com/en/docs/claude-code
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"language": "fr",
|
"language": "fr",
|
||||||
"silentMode": false,
|
"silentMode": false,
|
||||||
"ttsEnabled": false,
|
"ttsEnabled": false,
|
||||||
"maxQueueSize": 50
|
"maxQueueSize": 50
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"hyperfocusThresholdMinutes": 120,
|
"hyperfocusThresholdMinutes": 120,
|
||||||
"breakReminderIntervalMinutes": 45,
|
"breakReminderIntervalMinutes": 45,
|
||||||
"breakDurationMinutes": 10,
|
"breakDurationMinutes": 10,
|
||||||
"workdayStartHour": 9,
|
"workdayStartHour": 9,
|
||||||
"workdayEndHour": 18
|
"workdayEndHour": 18
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
526
src/main.cpp
526
src/main.cpp
@ -1,263 +1,263 @@
|
|||||||
#include <grove/ModuleLoader.h>
|
#include <grove/ModuleLoader.h>
|
||||||
#include <grove/JsonDataNode.h>
|
#include <grove/JsonDataNode.h>
|
||||||
#include <grove/IOFactory.h>
|
#include <grove/IOFactory.h>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
// Global flag for clean shutdown
|
// Global flag for clean shutdown
|
||||||
static volatile bool g_running = true;
|
static volatile bool g_running = true;
|
||||||
|
|
||||||
void signalHandler(int signal) {
|
void signalHandler(int signal) {
|
||||||
spdlog::info("Signal {} reçu, arrêt en cours...", signal);
|
spdlog::info("Signal {} reçu, arrêt en cours...", signal);
|
||||||
g_running = false;
|
g_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple file watcher for hot-reload
|
// Simple file watcher for hot-reload
|
||||||
class FileWatcher {
|
class FileWatcher {
|
||||||
public:
|
public:
|
||||||
void watch(const std::string& path) {
|
void watch(const std::string& path) {
|
||||||
if (fs::exists(path)) {
|
if (fs::exists(path)) {
|
||||||
m_lastModified[path] = fs::last_write_time(path);
|
m_lastModified[path] = fs::last_write_time(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasChanged(const std::string& path) {
|
bool hasChanged(const std::string& path) {
|
||||||
if (!fs::exists(path)) return false;
|
if (!fs::exists(path)) return false;
|
||||||
|
|
||||||
auto currentTime = fs::last_write_time(path);
|
auto currentTime = fs::last_write_time(path);
|
||||||
auto it = m_lastModified.find(path);
|
auto it = m_lastModified.find(path);
|
||||||
|
|
||||||
if (it == m_lastModified.end()) {
|
if (it == m_lastModified.end()) {
|
||||||
m_lastModified[path] = currentTime;
|
m_lastModified[path] = currentTime;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTime != it->second) {
|
if (currentTime != it->second) {
|
||||||
it->second = currentTime;
|
it->second = currentTime;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, fs::file_time_type> m_lastModified;
|
std::unordered_map<std::string, fs::file_time_type> m_lastModified;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load JSON config file
|
// Load JSON config file
|
||||||
std::unique_ptr<grove::JsonDataNode> loadConfig(const std::string& path) {
|
std::unique_ptr<grove::JsonDataNode> loadConfig(const std::string& path) {
|
||||||
if (fs::exists(path)) {
|
if (fs::exists(path)) {
|
||||||
std::ifstream file(path);
|
std::ifstream file(path);
|
||||||
nlohmann::json j;
|
nlohmann::json j;
|
||||||
file >> j;
|
file >> j;
|
||||||
auto config = std::make_unique<grove::JsonDataNode>("config", j);
|
auto config = std::make_unique<grove::JsonDataNode>("config", j);
|
||||||
spdlog::info("Config chargée: {}", path);
|
spdlog::info("Config chargée: {}", path);
|
||||||
return config;
|
return config;
|
||||||
} else {
|
} else {
|
||||||
spdlog::warn("Config non trouvée: {}, utilisation des défauts", path);
|
spdlog::warn("Config non trouvée: {}, utilisation des défauts", path);
|
||||||
return std::make_unique<grove::JsonDataNode>("config");
|
return std::make_unique<grove::JsonDataNode>("config");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module entry in our simple manager
|
// Module entry in our simple manager
|
||||||
struct ModuleEntry {
|
struct ModuleEntry {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string configFile;
|
std::string configFile;
|
||||||
std::string path;
|
std::string path;
|
||||||
std::unique_ptr<grove::ModuleLoader> loader;
|
std::unique_ptr<grove::ModuleLoader> loader;
|
||||||
std::unique_ptr<grove::IIO> io;
|
std::unique_ptr<grove::IIO> io;
|
||||||
grove::IModule* module = nullptr;
|
grove::IModule* module = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
// Setup logging
|
// Setup logging
|
||||||
auto console = spdlog::stdout_color_mt("Aissia");
|
auto console = spdlog::stdout_color_mt("Aissia");
|
||||||
spdlog::set_default_logger(console);
|
spdlog::set_default_logger(console);
|
||||||
spdlog::set_level(spdlog::level::debug);
|
spdlog::set_level(spdlog::level::debug);
|
||||||
spdlog::set_pattern("[%H:%M:%S.%e] [%n] [%^%l%$] %v");
|
spdlog::set_pattern("[%H:%M:%S.%e] [%n] [%^%l%$] %v");
|
||||||
|
|
||||||
spdlog::info("========================================");
|
spdlog::info("========================================");
|
||||||
spdlog::info(" AISSIA - Assistant Personnel IA");
|
spdlog::info(" AISSIA - Assistant Personnel IA");
|
||||||
spdlog::info(" Powered by GroveEngine");
|
spdlog::info(" Powered by GroveEngine");
|
||||||
spdlog::info("========================================");
|
spdlog::info("========================================");
|
||||||
|
|
||||||
// Signal handling
|
// Signal handling
|
||||||
std::signal(SIGINT, signalHandler);
|
std::signal(SIGINT, signalHandler);
|
||||||
std::signal(SIGTERM, signalHandler);
|
std::signal(SIGTERM, signalHandler);
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
const std::string modulesDir = "./modules/";
|
const std::string modulesDir = "./modules/";
|
||||||
const std::string configDir = "./config/";
|
const std::string configDir = "./config/";
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Module Management
|
// Module Management
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
std::map<std::string, ModuleEntry> modules;
|
std::map<std::string, ModuleEntry> modules;
|
||||||
FileWatcher watcher;
|
FileWatcher watcher;
|
||||||
|
|
||||||
// Liste des modules à charger
|
// Liste des modules à charger
|
||||||
std::vector<std::pair<std::string, std::string>> moduleList = {
|
std::vector<std::pair<std::string, std::string>> moduleList = {
|
||||||
{"SchedulerModule", "scheduler.json"},
|
{"SchedulerModule", "scheduler.json"},
|
||||||
{"NotificationModule", "notification.json"},
|
{"NotificationModule", "notification.json"},
|
||||||
// Futurs modules:
|
// Futurs modules:
|
||||||
// {"AIAssistantModule", "ai_assistant.json"},
|
// {"AIAssistantModule", "ai_assistant.json"},
|
||||||
// {"LanguageLearningModule", "language.json"},
|
// {"LanguageLearningModule", "language.json"},
|
||||||
// {"DataModule", "data.json"},
|
// {"DataModule", "data.json"},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Charger les modules
|
// Charger les modules
|
||||||
for (const auto& [moduleName, configFile] : moduleList) {
|
for (const auto& [moduleName, configFile] : moduleList) {
|
||||||
std::string modulePath = modulesDir + "lib" + moduleName + ".so";
|
std::string modulePath = modulesDir + "lib" + moduleName + ".so";
|
||||||
|
|
||||||
if (!fs::exists(modulePath)) {
|
if (!fs::exists(modulePath)) {
|
||||||
spdlog::warn("{} non trouvé: {}", moduleName, modulePath);
|
spdlog::warn("{} non trouvé: {}", moduleName, modulePath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleEntry entry;
|
ModuleEntry entry;
|
||||||
entry.name = moduleName;
|
entry.name = moduleName;
|
||||||
entry.configFile = configFile;
|
entry.configFile = configFile;
|
||||||
entry.path = modulePath;
|
entry.path = modulePath;
|
||||||
entry.loader = std::make_unique<grove::ModuleLoader>();
|
entry.loader = std::make_unique<grove::ModuleLoader>();
|
||||||
entry.io = grove::IOFactory::create("intra", moduleName);
|
entry.io = grove::IOFactory::create("intra", moduleName);
|
||||||
|
|
||||||
auto modulePtr = entry.loader->load(modulePath, moduleName);
|
auto modulePtr = entry.loader->load(modulePath, moduleName);
|
||||||
if (!modulePtr) {
|
if (!modulePtr) {
|
||||||
spdlog::error("Échec du chargement: {}", moduleName);
|
spdlog::error("Échec du chargement: {}", moduleName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure
|
// Configure
|
||||||
auto config = loadConfig(configDir + configFile);
|
auto config = loadConfig(configDir + configFile);
|
||||||
modulePtr->setConfiguration(*config, entry.io.get(), nullptr);
|
modulePtr->setConfiguration(*config, entry.io.get(), nullptr);
|
||||||
|
|
||||||
entry.module = modulePtr.release();
|
entry.module = modulePtr.release();
|
||||||
watcher.watch(modulePath);
|
watcher.watch(modulePath);
|
||||||
|
|
||||||
spdlog::info("{} chargé et configuré", moduleName);
|
spdlog::info("{} chargé et configuré", moduleName);
|
||||||
modules[moduleName] = std::move(entry);
|
modules[moduleName] = std::move(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modules.empty()) {
|
if (modules.empty()) {
|
||||||
spdlog::error("Aucun module chargé! Build les modules: cmake --build build --target modules");
|
spdlog::error("Aucun module chargé! Build les modules: cmake --build build --target modules");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Main Loop
|
// Main Loop
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
spdlog::info("Démarrage de la boucle principale (Ctrl+C pour quitter)");
|
spdlog::info("Démarrage de la boucle principale (Ctrl+C pour quitter)");
|
||||||
|
|
||||||
using Clock = std::chrono::high_resolution_clock;
|
using Clock = std::chrono::high_resolution_clock;
|
||||||
auto startTime = Clock::now();
|
auto startTime = Clock::now();
|
||||||
auto lastFrame = Clock::now();
|
auto lastFrame = Clock::now();
|
||||||
const auto targetFrameTime = std::chrono::milliseconds(100); // 10 Hz pour un assistant
|
const auto targetFrameTime = std::chrono::milliseconds(100); // 10 Hz pour un assistant
|
||||||
|
|
||||||
grove::JsonDataNode frameInput("frame");
|
grove::JsonDataNode frameInput("frame");
|
||||||
uint64_t frameCount = 0;
|
uint64_t frameCount = 0;
|
||||||
|
|
||||||
while (g_running) {
|
while (g_running) {
|
||||||
auto frameStart = Clock::now();
|
auto frameStart = Clock::now();
|
||||||
|
|
||||||
// Calculate times
|
// Calculate times
|
||||||
auto deltaTime = std::chrono::duration<float>(frameStart - lastFrame).count();
|
auto deltaTime = std::chrono::duration<float>(frameStart - lastFrame).count();
|
||||||
auto gameTime = std::chrono::duration<float>(frameStart - startTime).count();
|
auto gameTime = std::chrono::duration<float>(frameStart - startTime).count();
|
||||||
lastFrame = frameStart;
|
lastFrame = frameStart;
|
||||||
|
|
||||||
// Prepare frame input
|
// Prepare frame input
|
||||||
frameInput.setDouble("deltaTime", deltaTime);
|
frameInput.setDouble("deltaTime", deltaTime);
|
||||||
frameInput.setInt("frameCount", frameCount);
|
frameInput.setInt("frameCount", frameCount);
|
||||||
frameInput.setDouble("gameTime", gameTime);
|
frameInput.setDouble("gameTime", gameTime);
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Hot-Reload Check (every 10 frames = ~1 second)
|
// Hot-Reload Check (every 10 frames = ~1 second)
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
if (frameCount % 10 == 0) {
|
if (frameCount % 10 == 0) {
|
||||||
for (auto& [name, entry] : modules) {
|
for (auto& [name, entry] : modules) {
|
||||||
if (watcher.hasChanged(entry.path)) {
|
if (watcher.hasChanged(entry.path)) {
|
||||||
spdlog::info("Modification détectée: {}, hot-reload...", name);
|
spdlog::info("Modification détectée: {}, hot-reload...", name);
|
||||||
|
|
||||||
// Get state before reload
|
// Get state before reload
|
||||||
std::unique_ptr<grove::IDataNode> state;
|
std::unique_ptr<grove::IDataNode> state;
|
||||||
if (entry.module) {
|
if (entry.module) {
|
||||||
state = entry.module->getState();
|
state = entry.module->getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload
|
// Reload
|
||||||
auto reloaded = entry.loader->load(entry.path, name, true);
|
auto reloaded = entry.loader->load(entry.path, name, true);
|
||||||
if (reloaded) {
|
if (reloaded) {
|
||||||
auto config = loadConfig(configDir + entry.configFile);
|
auto config = loadConfig(configDir + entry.configFile);
|
||||||
reloaded->setConfiguration(*config, entry.io.get(), nullptr);
|
reloaded->setConfiguration(*config, entry.io.get(), nullptr);
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
reloaded->setState(*state);
|
reloaded->setState(*state);
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.module = reloaded.release();
|
entry.module = reloaded.release();
|
||||||
spdlog::info("{} rechargé avec succès!", name);
|
spdlog::info("{} rechargé avec succès!", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Process all modules
|
// Process all modules
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
for (auto& [name, entry] : modules) {
|
for (auto& [name, entry] : modules) {
|
||||||
if (entry.module) {
|
if (entry.module) {
|
||||||
entry.module->process(frameInput);
|
entry.module->process(frameInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process IO messages
|
// Process IO messages
|
||||||
for (auto& [name, entry] : modules) {
|
for (auto& [name, entry] : modules) {
|
||||||
while (entry.io && entry.io->hasMessages() > 0) {
|
while (entry.io && entry.io->hasMessages() > 0) {
|
||||||
auto msg = entry.io->pullMessage();
|
auto msg = entry.io->pullMessage();
|
||||||
// Route messages between modules if needed
|
// Route messages between modules if needed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Frame timing
|
// Frame timing
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
frameCount++;
|
frameCount++;
|
||||||
|
|
||||||
auto frameEnd = Clock::now();
|
auto frameEnd = Clock::now();
|
||||||
auto frameDuration = frameEnd - frameStart;
|
auto frameDuration = frameEnd - frameStart;
|
||||||
|
|
||||||
if (frameDuration < targetFrameTime) {
|
if (frameDuration < targetFrameTime) {
|
||||||
std::this_thread::sleep_for(targetFrameTime - frameDuration);
|
std::this_thread::sleep_for(targetFrameTime - frameDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status log every 30 seconds
|
// Status log every 30 seconds
|
||||||
if (frameCount % 300 == 0) {
|
if (frameCount % 300 == 0) {
|
||||||
int minutes = static_cast<int>(gameTime / 60.0f);
|
int minutes = static_cast<int>(gameTime / 60.0f);
|
||||||
int seconds = static_cast<int>(gameTime) % 60;
|
int seconds = static_cast<int>(gameTime) % 60;
|
||||||
spdlog::debug("Session: {}m{}s, {} modules actifs",
|
spdlog::debug("Session: {}m{}s, {} modules actifs",
|
||||||
minutes, seconds, modules.size());
|
minutes, seconds, modules.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Shutdown
|
// Shutdown
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
spdlog::info("Arrêt en cours...");
|
spdlog::info("Arrêt en cours...");
|
||||||
for (auto& [name, entry] : modules) {
|
for (auto& [name, entry] : modules) {
|
||||||
if (entry.module) {
|
if (entry.module) {
|
||||||
entry.module->shutdown();
|
entry.module->shutdown();
|
||||||
spdlog::info("{} arrêté", name);
|
spdlog::info("{} arrêté", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spdlog::info("À bientôt!");
|
spdlog::info("À bientôt!");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,172 +1,172 @@
|
|||||||
#include "NotificationModule.h"
|
#include "NotificationModule.h"
|
||||||
#include <grove/JsonDataNode.h>
|
#include <grove/JsonDataNode.h>
|
||||||
|
|
||||||
namespace aissia {
|
namespace aissia {
|
||||||
|
|
||||||
NotificationModule::NotificationModule() {
|
NotificationModule::NotificationModule() {
|
||||||
m_logger = spdlog::get("NotificationModule");
|
m_logger = spdlog::get("NotificationModule");
|
||||||
if (!m_logger) {
|
if (!m_logger) {
|
||||||
m_logger = spdlog::stdout_color_mt("NotificationModule");
|
m_logger = spdlog::stdout_color_mt("NotificationModule");
|
||||||
}
|
}
|
||||||
m_config = std::make_unique<grove::JsonDataNode>("config");
|
m_config = std::make_unique<grove::JsonDataNode>("config");
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationModule::setConfiguration(const grove::IDataNode& configNode,
|
void NotificationModule::setConfiguration(const grove::IDataNode& configNode,
|
||||||
grove::IIO* io,
|
grove::IIO* io,
|
||||||
grove::ITaskScheduler* scheduler) {
|
grove::ITaskScheduler* scheduler) {
|
||||||
m_io = io;
|
m_io = io;
|
||||||
m_config = std::make_unique<grove::JsonDataNode>("config");
|
m_config = std::make_unique<grove::JsonDataNode>("config");
|
||||||
|
|
||||||
// Charger la configuration
|
// Charger la configuration
|
||||||
m_language = configNode.getString("language", "fr");
|
m_language = configNode.getString("language", "fr");
|
||||||
m_silentMode = configNode.getBool("silentMode", false);
|
m_silentMode = configNode.getBool("silentMode", false);
|
||||||
m_ttsEnabled = configNode.getBool("ttsEnabled", false);
|
m_ttsEnabled = configNode.getBool("ttsEnabled", false);
|
||||||
m_maxQueueSize = configNode.getInt("maxQueueSize", 50);
|
m_maxQueueSize = configNode.getInt("maxQueueSize", 50);
|
||||||
|
|
||||||
m_logger->info("NotificationModule configuré: langue={}, silent={}, tts={}",
|
m_logger->info("NotificationModule configuré: langue={}, silent={}, tts={}",
|
||||||
m_language, m_silentMode, m_ttsEnabled);
|
m_language, m_silentMode, m_ttsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
const grove::IDataNode& NotificationModule::getConfiguration() {
|
const grove::IDataNode& NotificationModule::getConfiguration() {
|
||||||
return *m_config;
|
return *m_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationModule::process(const grove::IDataNode& input) {
|
void NotificationModule::process(const grove::IDataNode& input) {
|
||||||
// Traiter la file de notifications
|
// Traiter la file de notifications
|
||||||
processNotificationQueue();
|
processNotificationQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationModule::notify(const std::string& title, const std::string& message, Priority priority) {
|
void NotificationModule::notify(const std::string& title, const std::string& message, Priority priority) {
|
||||||
if (m_silentMode && priority != Priority::URGENT) {
|
if (m_silentMode && priority != Priority::URGENT) {
|
||||||
return; // Ignorer les notifications non-urgentes en mode silencieux
|
return; // Ignorer les notifications non-urgentes en mode silencieux
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification notif;
|
Notification notif;
|
||||||
notif.id = std::to_string(++m_notificationCount);
|
notif.id = std::to_string(++m_notificationCount);
|
||||||
notif.title = title;
|
notif.title = title;
|
||||||
notif.message = message;
|
notif.message = message;
|
||||||
notif.priority = priority;
|
notif.priority = priority;
|
||||||
notif.language = m_language;
|
notif.language = m_language;
|
||||||
notif.read = false;
|
notif.read = false;
|
||||||
notif.timestamp = 0; // Sera mis à jour lors du process
|
notif.timestamp = 0; // Sera mis à jour lors du process
|
||||||
|
|
||||||
// Limiter la taille de la queue
|
// Limiter la taille de la queue
|
||||||
while (static_cast<int>(m_pendingNotifications.size()) >= m_maxQueueSize) {
|
while (static_cast<int>(m_pendingNotifications.size()) >= m_maxQueueSize) {
|
||||||
m_pendingNotifications.pop();
|
m_pendingNotifications.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pendingNotifications.push(notif);
|
m_pendingNotifications.push(notif);
|
||||||
|
|
||||||
if (priority == Priority::URGENT) {
|
if (priority == Priority::URGENT) {
|
||||||
m_urgentCount++;
|
m_urgentCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationModule::processNotificationQueue() {
|
void NotificationModule::processNotificationQueue() {
|
||||||
// Traiter jusqu'à 3 notifications par frame pour éviter le spam
|
// Traiter jusqu'à 3 notifications par frame pour éviter le spam
|
||||||
int processed = 0;
|
int processed = 0;
|
||||||
while (!m_pendingNotifications.empty() && processed < 3) {
|
while (!m_pendingNotifications.empty() && processed < 3) {
|
||||||
Notification notif = m_pendingNotifications.front();
|
Notification notif = m_pendingNotifications.front();
|
||||||
m_pendingNotifications.pop();
|
m_pendingNotifications.pop();
|
||||||
|
|
||||||
displayNotification(notif);
|
displayNotification(notif);
|
||||||
processed++;
|
processed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationModule::displayNotification(const Notification& notif) {
|
void NotificationModule::displayNotification(const Notification& notif) {
|
||||||
std::string emoji = priorityToEmoji(notif.priority);
|
std::string emoji = priorityToEmoji(notif.priority);
|
||||||
std::string priorityStr = priorityToString(notif.priority);
|
std::string priorityStr = priorityToString(notif.priority);
|
||||||
|
|
||||||
// Affichage console (sera remplacé par Windows toast + TTS)
|
// Affichage console (sera remplacé par Windows toast + TTS)
|
||||||
switch (notif.priority) {
|
switch (notif.priority) {
|
||||||
case Priority::URGENT:
|
case Priority::URGENT:
|
||||||
m_logger->warn("{} [{}] {}: {}", emoji, priorityStr, notif.title, notif.message);
|
m_logger->warn("{} [{}] {}: {}", emoji, priorityStr, notif.title, notif.message);
|
||||||
break;
|
break;
|
||||||
case Priority::HIGH:
|
case Priority::HIGH:
|
||||||
m_logger->info("{} [{}] {}: {}", emoji, priorityStr, notif.title, notif.message);
|
m_logger->info("{} [{}] {}: {}", emoji, priorityStr, notif.title, notif.message);
|
||||||
break;
|
break;
|
||||||
case Priority::NORMAL:
|
case Priority::NORMAL:
|
||||||
m_logger->info("{} {}: {}", emoji, notif.title, notif.message);
|
m_logger->info("{} {}: {}", emoji, notif.title, notif.message);
|
||||||
break;
|
break;
|
||||||
case Priority::LOW:
|
case Priority::LOW:
|
||||||
m_logger->debug("{} {}: {}", emoji, notif.title, notif.message);
|
m_logger->debug("{} {}: {}", emoji, notif.title, notif.message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Intégrer Windows Toast notifications
|
// TODO: Intégrer Windows Toast notifications
|
||||||
// TODO: Intégrer TTS si m_ttsEnabled
|
// TODO: Intégrer TTS si m_ttsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NotificationModule::priorityToString(Priority p) {
|
std::string NotificationModule::priorityToString(Priority p) {
|
||||||
switch (p) {
|
switch (p) {
|
||||||
case Priority::LOW: return "INFO";
|
case Priority::LOW: return "INFO";
|
||||||
case Priority::NORMAL: return "RAPPEL";
|
case Priority::NORMAL: return "RAPPEL";
|
||||||
case Priority::HIGH: return "IMPORTANT";
|
case Priority::HIGH: return "IMPORTANT";
|
||||||
case Priority::URGENT: return "URGENT";
|
case Priority::URGENT: return "URGENT";
|
||||||
default: return "?";
|
default: return "?";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NotificationModule::priorityToEmoji(Priority p) {
|
std::string NotificationModule::priorityToEmoji(Priority p) {
|
||||||
switch (p) {
|
switch (p) {
|
||||||
case Priority::LOW: return "[i]";
|
case Priority::LOW: return "[i]";
|
||||||
case Priority::NORMAL: return "[*]";
|
case Priority::NORMAL: return "[*]";
|
||||||
case Priority::HIGH: return "[!]";
|
case Priority::HIGH: return "[!]";
|
||||||
case Priority::URGENT: return "[!!!]";
|
case Priority::URGENT: return "[!!!]";
|
||||||
default: return "[ ]";
|
default: return "[ ]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<grove::IDataNode> NotificationModule::getHealthStatus() {
|
std::unique_ptr<grove::IDataNode> NotificationModule::getHealthStatus() {
|
||||||
auto status = std::make_unique<grove::JsonDataNode>("status");
|
auto status = std::make_unique<grove::JsonDataNode>("status");
|
||||||
status->setString("status", "running");
|
status->setString("status", "running");
|
||||||
status->setInt("pendingCount", m_pendingNotifications.size());
|
status->setInt("pendingCount", m_pendingNotifications.size());
|
||||||
status->setInt("totalNotifications", m_notificationCount);
|
status->setInt("totalNotifications", m_notificationCount);
|
||||||
status->setInt("urgentCount", m_urgentCount);
|
status->setInt("urgentCount", m_urgentCount);
|
||||||
status->setBool("silentMode", m_silentMode);
|
status->setBool("silentMode", m_silentMode);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationModule::shutdown() {
|
void NotificationModule::shutdown() {
|
||||||
// Vider la queue avant arrêt
|
// Vider la queue avant arrêt
|
||||||
while (!m_pendingNotifications.empty()) {
|
while (!m_pendingNotifications.empty()) {
|
||||||
displayNotification(m_pendingNotifications.front());
|
displayNotification(m_pendingNotifications.front());
|
||||||
m_pendingNotifications.pop();
|
m_pendingNotifications.pop();
|
||||||
}
|
}
|
||||||
m_logger->info("NotificationModule arrêté. Total: {} notifications ({} urgentes)",
|
m_logger->info("NotificationModule arrêté. Total: {} notifications ({} urgentes)",
|
||||||
m_notificationCount, m_urgentCount);
|
m_notificationCount, m_urgentCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<grove::IDataNode> NotificationModule::getState() {
|
std::unique_ptr<grove::IDataNode> NotificationModule::getState() {
|
||||||
auto state = std::make_unique<grove::JsonDataNode>("state");
|
auto state = std::make_unique<grove::JsonDataNode>("state");
|
||||||
|
|
||||||
state->setInt("notificationCount", m_notificationCount);
|
state->setInt("notificationCount", m_notificationCount);
|
||||||
state->setInt("urgentCount", m_urgentCount);
|
state->setInt("urgentCount", m_urgentCount);
|
||||||
state->setInt("pendingCount", m_pendingNotifications.size());
|
state->setInt("pendingCount", m_pendingNotifications.size());
|
||||||
|
|
||||||
m_logger->debug("État sauvegardé: {} notifications", m_notificationCount);
|
m_logger->debug("État sauvegardé: {} notifications", m_notificationCount);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationModule::setState(const grove::IDataNode& state) {
|
void NotificationModule::setState(const grove::IDataNode& state) {
|
||||||
m_notificationCount = state.getInt("notificationCount", 0);
|
m_notificationCount = state.getInt("notificationCount", 0);
|
||||||
m_urgentCount = state.getInt("urgentCount", 0);
|
m_urgentCount = state.getInt("urgentCount", 0);
|
||||||
// Note: On ne restaure pas la queue - les notifications en attente sont perdues au reload
|
// Note: On ne restaure pas la queue - les notifications en attente sont perdues au reload
|
||||||
|
|
||||||
m_logger->info("État restauré: {} notifications historiques", m_notificationCount);
|
m_logger->info("État restauré: {} notifications historiques", m_notificationCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace aissia
|
} // namespace aissia
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
grove::IModule* createModule() {
|
grove::IModule* createModule() {
|
||||||
return new aissia::NotificationModule();
|
return new aissia::NotificationModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroyModule(grove::IModule* module) {
|
void destroyModule(grove::IModule* module) {
|
||||||
delete module;
|
delete module;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,96 +1,96 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <grove/IModule.h>
|
#include <grove/IModule.h>
|
||||||
#include <grove/JsonDataNode.h>
|
#include <grove/JsonDataNode.h>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
namespace aissia {
|
namespace aissia {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Notification Module - Alertes système et TTS
|
* @brief Notification Module - Alertes système et TTS
|
||||||
*
|
*
|
||||||
* Fonctionnalités:
|
* Fonctionnalités:
|
||||||
* - Notifications système (console pour l'instant, Windows toast plus tard)
|
* - Notifications système (console pour l'instant, Windows toast plus tard)
|
||||||
* - File d'attente des notifications avec priorités
|
* - File d'attente des notifications avec priorités
|
||||||
* - Support multilingue (FR/EN/JP selon config)
|
* - Support multilingue (FR/EN/JP selon config)
|
||||||
* - Mode silencieux configurable
|
* - Mode silencieux configurable
|
||||||
*
|
*
|
||||||
* Souscrit à:
|
* Souscrit à:
|
||||||
* - "scheduler/hyperfocus_alert" : Alerte hyperfocus -> notification urgente
|
* - "scheduler/hyperfocus_alert" : Alerte hyperfocus -> notification urgente
|
||||||
* - "scheduler/break_reminder" : Rappel pause -> notification normale
|
* - "scheduler/break_reminder" : Rappel pause -> notification normale
|
||||||
* - "ai/suggestion" : Suggestion IA -> notification info
|
* - "ai/suggestion" : Suggestion IA -> notification info
|
||||||
* - "language/correction" : Correction langue -> notification légère
|
* - "language/correction" : Correction langue -> notification légère
|
||||||
*/
|
*/
|
||||||
class NotificationModule : public grove::IModule {
|
class NotificationModule : public grove::IModule {
|
||||||
public:
|
public:
|
||||||
enum class Priority {
|
enum class Priority {
|
||||||
LOW, // Info, peut être ignorée
|
LOW, // Info, peut être ignorée
|
||||||
NORMAL, // Rappels standards
|
NORMAL, // Rappels standards
|
||||||
HIGH, // Alertes importantes
|
HIGH, // Alertes importantes
|
||||||
URGENT // Hyperfocus, urgences
|
URGENT // Hyperfocus, urgences
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Notification {
|
struct Notification {
|
||||||
std::string id;
|
std::string id;
|
||||||
std::string title;
|
std::string title;
|
||||||
std::string message;
|
std::string message;
|
||||||
Priority priority;
|
Priority priority;
|
||||||
std::string language; // "fr", "en", "jp"
|
std::string language; // "fr", "en", "jp"
|
||||||
bool read;
|
bool read;
|
||||||
float timestamp;
|
float timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
NotificationModule();
|
NotificationModule();
|
||||||
~NotificationModule() override = default;
|
~NotificationModule() override = default;
|
||||||
|
|
||||||
// IModule interface
|
// IModule interface
|
||||||
void process(const grove::IDataNode& input) override;
|
void process(const grove::IDataNode& input) override;
|
||||||
void setConfiguration(const grove::IDataNode& configNode, grove::IIO* io,
|
void setConfiguration(const grove::IDataNode& configNode, grove::IIO* io,
|
||||||
grove::ITaskScheduler* scheduler) override;
|
grove::ITaskScheduler* scheduler) override;
|
||||||
const grove::IDataNode& getConfiguration() override;
|
const grove::IDataNode& getConfiguration() override;
|
||||||
std::unique_ptr<grove::IDataNode> getHealthStatus() override;
|
std::unique_ptr<grove::IDataNode> getHealthStatus() override;
|
||||||
void shutdown() override;
|
void shutdown() override;
|
||||||
std::unique_ptr<grove::IDataNode> getState() override;
|
std::unique_ptr<grove::IDataNode> getState() override;
|
||||||
void setState(const grove::IDataNode& state) override;
|
void setState(const grove::IDataNode& state) override;
|
||||||
std::string getType() const override { return "NotificationModule"; }
|
std::string getType() const override { return "NotificationModule"; }
|
||||||
bool isIdle() const override { return true; }
|
bool isIdle() const override { return true; }
|
||||||
int getVersion() const override { return 1; }
|
int getVersion() const override { return 1; }
|
||||||
|
|
||||||
// API publique
|
// API publique
|
||||||
void notify(const std::string& title, const std::string& message, Priority priority = Priority::NORMAL);
|
void notify(const std::string& title, const std::string& message, Priority priority = Priority::NORMAL);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Configuration
|
// Configuration
|
||||||
std::string m_language = "fr";
|
std::string m_language = "fr";
|
||||||
bool m_silentMode = false;
|
bool m_silentMode = false;
|
||||||
bool m_ttsEnabled = false; // Text-to-Speech (future)
|
bool m_ttsEnabled = false; // Text-to-Speech (future)
|
||||||
int m_maxQueueSize = 50;
|
int m_maxQueueSize = 50;
|
||||||
|
|
||||||
// État
|
// État
|
||||||
std::queue<Notification> m_pendingNotifications;
|
std::queue<Notification> m_pendingNotifications;
|
||||||
int m_notificationCount = 0;
|
int m_notificationCount = 0;
|
||||||
int m_urgentCount = 0;
|
int m_urgentCount = 0;
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
grove::IIO* m_io = nullptr;
|
grove::IIO* m_io = nullptr;
|
||||||
std::unique_ptr<grove::JsonDataNode> m_config;
|
std::unique_ptr<grove::JsonDataNode> m_config;
|
||||||
std::shared_ptr<spdlog::logger> m_logger;
|
std::shared_ptr<spdlog::logger> m_logger;
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
void processNotificationQueue();
|
void processNotificationQueue();
|
||||||
void displayNotification(const Notification& notif);
|
void displayNotification(const Notification& notif);
|
||||||
std::string priorityToString(Priority p);
|
std::string priorityToString(Priority p);
|
||||||
std::string priorityToEmoji(Priority p);
|
std::string priorityToEmoji(Priority p);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace aissia
|
} // namespace aissia
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
grove::IModule* createModule();
|
grove::IModule* createModule();
|
||||||
void destroyModule(grove::IModule* module);
|
void destroyModule(grove::IModule* module);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,179 +1,179 @@
|
|||||||
#include "SchedulerModule.h"
|
#include "SchedulerModule.h"
|
||||||
#include <grove/JsonDataNode.h>
|
#include <grove/JsonDataNode.h>
|
||||||
|
|
||||||
namespace aissia {
|
namespace aissia {
|
||||||
|
|
||||||
SchedulerModule::SchedulerModule() {
|
SchedulerModule::SchedulerModule() {
|
||||||
m_logger = spdlog::get("SchedulerModule");
|
m_logger = spdlog::get("SchedulerModule");
|
||||||
if (!m_logger) {
|
if (!m_logger) {
|
||||||
m_logger = spdlog::stdout_color_mt("SchedulerModule");
|
m_logger = spdlog::stdout_color_mt("SchedulerModule");
|
||||||
}
|
}
|
||||||
m_config = std::make_unique<grove::JsonDataNode>("config");
|
m_config = std::make_unique<grove::JsonDataNode>("config");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SchedulerModule::setConfiguration(const grove::IDataNode& configNode,
|
void SchedulerModule::setConfiguration(const grove::IDataNode& configNode,
|
||||||
grove::IIO* io,
|
grove::IIO* io,
|
||||||
grove::ITaskScheduler* scheduler) {
|
grove::ITaskScheduler* scheduler) {
|
||||||
m_io = io;
|
m_io = io;
|
||||||
m_config = std::make_unique<grove::JsonDataNode>("config");
|
m_config = std::make_unique<grove::JsonDataNode>("config");
|
||||||
|
|
||||||
// Charger la configuration
|
// Charger la configuration
|
||||||
m_hyperfocusThresholdMinutes = configNode.getInt("hyperfocusThresholdMinutes", 120);
|
m_hyperfocusThresholdMinutes = configNode.getInt("hyperfocusThresholdMinutes", 120);
|
||||||
m_breakReminderIntervalMinutes = configNode.getInt("breakReminderIntervalMinutes", 45);
|
m_breakReminderIntervalMinutes = configNode.getInt("breakReminderIntervalMinutes", 45);
|
||||||
m_breakDurationMinutes = configNode.getInt("breakDurationMinutes", 10);
|
m_breakDurationMinutes = configNode.getInt("breakDurationMinutes", 10);
|
||||||
|
|
||||||
m_logger->info("SchedulerModule configuré: hyperfocus={}min, break_interval={}min",
|
m_logger->info("SchedulerModule configuré: hyperfocus={}min, break_interval={}min",
|
||||||
m_hyperfocusThresholdMinutes, m_breakReminderIntervalMinutes);
|
m_hyperfocusThresholdMinutes, m_breakReminderIntervalMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
const grove::IDataNode& SchedulerModule::getConfiguration() {
|
const grove::IDataNode& SchedulerModule::getConfiguration() {
|
||||||
return *m_config;
|
return *m_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SchedulerModule::process(const grove::IDataNode& input) {
|
void SchedulerModule::process(const grove::IDataNode& input) {
|
||||||
float currentTime = input.getDouble("gameTime", 0.0);
|
float currentTime = input.getDouble("gameTime", 0.0);
|
||||||
float dt = input.getDouble("deltaTime", 0.016);
|
float dt = input.getDouble("deltaTime", 0.016);
|
||||||
|
|
||||||
// Convertir le temps en minutes pour les calculs
|
// Convertir le temps en minutes pour les calculs
|
||||||
float sessionMinutes = (currentTime - m_sessionStartTime) / 60.0f;
|
float sessionMinutes = (currentTime - m_sessionStartTime) / 60.0f;
|
||||||
|
|
||||||
// Vérifier l'hyperfocus
|
// Vérifier l'hyperfocus
|
||||||
checkHyperfocus(currentTime);
|
checkHyperfocus(currentTime);
|
||||||
|
|
||||||
// Vérifier les rappels de pause
|
// Vérifier les rappels de pause
|
||||||
checkBreakReminder(currentTime);
|
checkBreakReminder(currentTime);
|
||||||
|
|
||||||
// Log périodique (toutes les 5 minutes simulées)
|
// Log périodique (toutes les 5 minutes simulées)
|
||||||
static float lastLog = 0;
|
static float lastLog = 0;
|
||||||
if (currentTime - lastLog > 300.0f) { // 300 secondes = 5 minutes
|
if (currentTime - lastLog > 300.0f) { // 300 secondes = 5 minutes
|
||||||
lastLog = currentTime;
|
lastLog = currentTime;
|
||||||
m_logger->debug("Session: {:.1f}min, Focus aujourd'hui: {}min, Tâche: {}",
|
m_logger->debug("Session: {:.1f}min, Focus aujourd'hui: {}min, Tâche: {}",
|
||||||
sessionMinutes, m_totalFocusMinutesToday,
|
sessionMinutes, m_totalFocusMinutesToday,
|
||||||
m_currentTaskId.empty() ? "(aucune)" : m_currentTaskId);
|
m_currentTaskId.empty() ? "(aucune)" : m_currentTaskId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SchedulerModule::checkHyperfocus(float currentTime) {
|
void SchedulerModule::checkHyperfocus(float currentTime) {
|
||||||
if (m_currentTaskId.empty()) return;
|
if (m_currentTaskId.empty()) return;
|
||||||
|
|
||||||
float sessionMinutes = (currentTime - m_sessionStartTime) / 60.0f;
|
float sessionMinutes = (currentTime - m_sessionStartTime) / 60.0f;
|
||||||
|
|
||||||
if (sessionMinutes >= m_hyperfocusThresholdMinutes && !m_hyperfocusAlertSent) {
|
if (sessionMinutes >= m_hyperfocusThresholdMinutes && !m_hyperfocusAlertSent) {
|
||||||
m_hyperfocusAlertSent = true;
|
m_hyperfocusAlertSent = true;
|
||||||
m_logger->warn("HYPERFOCUS DÉTECTÉ! Session de {:.0f} minutes sur '{}'",
|
m_logger->warn("HYPERFOCUS DÉTECTÉ! Session de {:.0f} minutes sur '{}'",
|
||||||
sessionMinutes, m_currentTaskId);
|
sessionMinutes, m_currentTaskId);
|
||||||
|
|
||||||
// Publier l'alerte (si IO disponible)
|
// Publier l'alerte (si IO disponible)
|
||||||
// Note: Dans une version complète, on publierait via m_io
|
// Note: Dans une version complète, on publierait via m_io
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SchedulerModule::checkBreakReminder(float currentTime) {
|
void SchedulerModule::checkBreakReminder(float currentTime) {
|
||||||
float timeSinceBreak = (currentTime - m_lastBreakTime) / 60.0f;
|
float timeSinceBreak = (currentTime - m_lastBreakTime) / 60.0f;
|
||||||
|
|
||||||
if (timeSinceBreak >= m_breakReminderIntervalMinutes) {
|
if (timeSinceBreak >= m_breakReminderIntervalMinutes) {
|
||||||
m_lastBreakTime = currentTime;
|
m_lastBreakTime = currentTime;
|
||||||
m_logger->info("RAPPEL: Pause de {} minutes recommandée!", m_breakDurationMinutes);
|
m_logger->info("RAPPEL: Pause de {} minutes recommandée!", m_breakDurationMinutes);
|
||||||
|
|
||||||
// Publier le rappel (si IO disponible)
|
// Publier le rappel (si IO disponible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SchedulerModule::startTask(const std::string& taskId) {
|
void SchedulerModule::startTask(const std::string& taskId) {
|
||||||
// Compléter la tâche précédente si nécessaire
|
// Compléter la tâche précédente si nécessaire
|
||||||
if (!m_currentTaskId.empty()) {
|
if (!m_currentTaskId.empty()) {
|
||||||
completeCurrentTask();
|
completeCurrentTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentTaskId = taskId;
|
m_currentTaskId = taskId;
|
||||||
m_sessionStartTime = m_lastActivityTime;
|
m_sessionStartTime = m_lastActivityTime;
|
||||||
m_hyperfocusAlertSent = false;
|
m_hyperfocusAlertSent = false;
|
||||||
|
|
||||||
Task* task = findTask(taskId);
|
Task* task = findTask(taskId);
|
||||||
if (task) {
|
if (task) {
|
||||||
m_logger->info("Tâche démarrée: {} (estimé: {}min)", task->name, task->estimatedMinutes);
|
m_logger->info("Tâche démarrée: {} (estimé: {}min)", task->name, task->estimatedMinutes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SchedulerModule::completeCurrentTask() {
|
void SchedulerModule::completeCurrentTask() {
|
||||||
if (m_currentTaskId.empty()) return;
|
if (m_currentTaskId.empty()) return;
|
||||||
|
|
||||||
Task* task = findTask(m_currentTaskId);
|
Task* task = findTask(m_currentTaskId);
|
||||||
if (task) {
|
if (task) {
|
||||||
float sessionMinutes = (m_lastActivityTime - m_sessionStartTime) / 60.0f;
|
float sessionMinutes = (m_lastActivityTime - m_sessionStartTime) / 60.0f;
|
||||||
task->actualMinutes = static_cast<int>(sessionMinutes);
|
task->actualMinutes = static_cast<int>(sessionMinutes);
|
||||||
task->completed = true;
|
task->completed = true;
|
||||||
m_totalFocusMinutesToday += task->actualMinutes;
|
m_totalFocusMinutesToday += task->actualMinutes;
|
||||||
|
|
||||||
m_logger->info("Tâche terminée: {} (réel: {}min vs estimé: {}min)",
|
m_logger->info("Tâche terminée: {} (réel: {}min vs estimé: {}min)",
|
||||||
task->name, task->actualMinutes, task->estimatedMinutes);
|
task->name, task->actualMinutes, task->estimatedMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentTaskId.clear();
|
m_currentTaskId.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
SchedulerModule::Task* SchedulerModule::findTask(const std::string& taskId) {
|
SchedulerModule::Task* SchedulerModule::findTask(const std::string& taskId) {
|
||||||
for (auto& task : m_tasks) {
|
for (auto& task : m_tasks) {
|
||||||
if (task.id == taskId) return &task;
|
if (task.id == taskId) return &task;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<grove::IDataNode> SchedulerModule::getHealthStatus() {
|
std::unique_ptr<grove::IDataNode> SchedulerModule::getHealthStatus() {
|
||||||
auto status = std::make_unique<grove::JsonDataNode>("status");
|
auto status = std::make_unique<grove::JsonDataNode>("status");
|
||||||
status->setString("status", "running");
|
status->setString("status", "running");
|
||||||
status->setInt("taskCount", m_tasks.size());
|
status->setInt("taskCount", m_tasks.size());
|
||||||
status->setString("currentTask", m_currentTaskId);
|
status->setString("currentTask", m_currentTaskId);
|
||||||
status->setInt("totalFocusMinutesToday", m_totalFocusMinutesToday);
|
status->setInt("totalFocusMinutesToday", m_totalFocusMinutesToday);
|
||||||
status->setBool("hyperfocusAlertSent", m_hyperfocusAlertSent);
|
status->setBool("hyperfocusAlertSent", m_hyperfocusAlertSent);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SchedulerModule::shutdown() {
|
void SchedulerModule::shutdown() {
|
||||||
if (!m_currentTaskId.empty()) {
|
if (!m_currentTaskId.empty()) {
|
||||||
completeCurrentTask();
|
completeCurrentTask();
|
||||||
}
|
}
|
||||||
m_logger->info("SchedulerModule arrêté. Focus total: {}min", m_totalFocusMinutesToday);
|
m_logger->info("SchedulerModule arrêté. Focus total: {}min", m_totalFocusMinutesToday);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<grove::IDataNode> SchedulerModule::getState() {
|
std::unique_ptr<grove::IDataNode> SchedulerModule::getState() {
|
||||||
auto state = std::make_unique<grove::JsonDataNode>("state");
|
auto state = std::make_unique<grove::JsonDataNode>("state");
|
||||||
|
|
||||||
state->setString("currentTaskId", m_currentTaskId);
|
state->setString("currentTaskId", m_currentTaskId);
|
||||||
state->setDouble("sessionStartTime", m_sessionStartTime);
|
state->setDouble("sessionStartTime", m_sessionStartTime);
|
||||||
state->setDouble("lastBreakTime", m_lastBreakTime);
|
state->setDouble("lastBreakTime", m_lastBreakTime);
|
||||||
state->setDouble("lastActivityTime", m_lastActivityTime);
|
state->setDouble("lastActivityTime", m_lastActivityTime);
|
||||||
state->setBool("hyperfocusAlertSent", m_hyperfocusAlertSent);
|
state->setBool("hyperfocusAlertSent", m_hyperfocusAlertSent);
|
||||||
state->setInt("totalFocusMinutesToday", m_totalFocusMinutesToday);
|
state->setInt("totalFocusMinutesToday", m_totalFocusMinutesToday);
|
||||||
state->setInt("taskCount", m_tasks.size());
|
state->setInt("taskCount", m_tasks.size());
|
||||||
|
|
||||||
m_logger->debug("État sauvegardé: {} tâches, focus={}min", m_tasks.size(), m_totalFocusMinutesToday);
|
m_logger->debug("État sauvegardé: {} tâches, focus={}min", m_tasks.size(), m_totalFocusMinutesToday);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SchedulerModule::setState(const grove::IDataNode& state) {
|
void SchedulerModule::setState(const grove::IDataNode& state) {
|
||||||
m_currentTaskId = state.getString("currentTaskId", "");
|
m_currentTaskId = state.getString("currentTaskId", "");
|
||||||
m_sessionStartTime = state.getDouble("sessionStartTime", 0.0);
|
m_sessionStartTime = state.getDouble("sessionStartTime", 0.0);
|
||||||
m_lastBreakTime = state.getDouble("lastBreakTime", 0.0);
|
m_lastBreakTime = state.getDouble("lastBreakTime", 0.0);
|
||||||
m_lastActivityTime = state.getDouble("lastActivityTime", 0.0);
|
m_lastActivityTime = state.getDouble("lastActivityTime", 0.0);
|
||||||
m_hyperfocusAlertSent = state.getBool("hyperfocusAlertSent", false);
|
m_hyperfocusAlertSent = state.getBool("hyperfocusAlertSent", false);
|
||||||
m_totalFocusMinutesToday = state.getInt("totalFocusMinutesToday", 0);
|
m_totalFocusMinutesToday = state.getInt("totalFocusMinutesToday", 0);
|
||||||
|
|
||||||
m_logger->info("État restauré: tâche='{}', focus={}min",
|
m_logger->info("État restauré: tâche='{}', focus={}min",
|
||||||
m_currentTaskId.empty() ? "(aucune)" : m_currentTaskId,
|
m_currentTaskId.empty() ? "(aucune)" : m_currentTaskId,
|
||||||
m_totalFocusMinutesToday);
|
m_totalFocusMinutesToday);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace aissia
|
} // namespace aissia
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
grove::IModule* createModule() {
|
grove::IModule* createModule() {
|
||||||
return new aissia::SchedulerModule();
|
return new aissia::SchedulerModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroyModule(grove::IModule* module) {
|
void destroyModule(grove::IModule* module) {
|
||||||
delete module;
|
delete module;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,94 +1,94 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <grove/IModule.h>
|
#include <grove/IModule.h>
|
||||||
#include <grove/JsonDataNode.h>
|
#include <grove/JsonDataNode.h>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
namespace aissia {
|
namespace aissia {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Scheduler Module - Gestion du temps et détection d'hyperfocus
|
* @brief Scheduler Module - Gestion du temps et détection d'hyperfocus
|
||||||
*
|
*
|
||||||
* Fonctionnalités:
|
* Fonctionnalités:
|
||||||
* - Planning de tâches avec durées estimées
|
* - Planning de tâches avec durées estimées
|
||||||
* - Détection d'hyperfocus (session > seuil configurable)
|
* - Détection d'hyperfocus (session > seuil configurable)
|
||||||
* - Rappels de pauses automatiques
|
* - Rappels de pauses automatiques
|
||||||
* - Tracking du temps par tâche
|
* - Tracking du temps par tâche
|
||||||
*
|
*
|
||||||
* Publie sur:
|
* Publie sur:
|
||||||
* - "scheduler/hyperfocus_alert" : Alerte quand hyperfocus détecté
|
* - "scheduler/hyperfocus_alert" : Alerte quand hyperfocus détecté
|
||||||
* - "scheduler/break_reminder" : Rappel de pause
|
* - "scheduler/break_reminder" : Rappel de pause
|
||||||
* - "scheduler/task_started" : Début de tâche
|
* - "scheduler/task_started" : Début de tâche
|
||||||
* - "scheduler/task_completed" : Fin de tâche
|
* - "scheduler/task_completed" : Fin de tâche
|
||||||
*
|
*
|
||||||
* Souscrit à:
|
* Souscrit à:
|
||||||
* - "user/activity" : Activité utilisateur (reset idle)
|
* - "user/activity" : Activité utilisateur (reset idle)
|
||||||
* - "user/task_switch" : Changement de tâche manuel
|
* - "user/task_switch" : Changement de tâche manuel
|
||||||
*/
|
*/
|
||||||
class SchedulerModule : public grove::IModule {
|
class SchedulerModule : public grove::IModule {
|
||||||
public:
|
public:
|
||||||
struct Task {
|
struct Task {
|
||||||
std::string id;
|
std::string id;
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string category;
|
std::string category;
|
||||||
int estimatedMinutes;
|
int estimatedMinutes;
|
||||||
int actualMinutes;
|
int actualMinutes;
|
||||||
bool completed;
|
bool completed;
|
||||||
};
|
};
|
||||||
|
|
||||||
SchedulerModule();
|
SchedulerModule();
|
||||||
~SchedulerModule() override = default;
|
~SchedulerModule() override = default;
|
||||||
|
|
||||||
// IModule interface
|
// IModule interface
|
||||||
void process(const grove::IDataNode& input) override;
|
void process(const grove::IDataNode& input) override;
|
||||||
void setConfiguration(const grove::IDataNode& configNode, grove::IIO* io,
|
void setConfiguration(const grove::IDataNode& configNode, grove::IIO* io,
|
||||||
grove::ITaskScheduler* scheduler) override;
|
grove::ITaskScheduler* scheduler) override;
|
||||||
const grove::IDataNode& getConfiguration() override;
|
const grove::IDataNode& getConfiguration() override;
|
||||||
std::unique_ptr<grove::IDataNode> getHealthStatus() override;
|
std::unique_ptr<grove::IDataNode> getHealthStatus() override;
|
||||||
void shutdown() override;
|
void shutdown() override;
|
||||||
std::unique_ptr<grove::IDataNode> getState() override;
|
std::unique_ptr<grove::IDataNode> getState() override;
|
||||||
void setState(const grove::IDataNode& state) override;
|
void setState(const grove::IDataNode& state) override;
|
||||||
std::string getType() const override { return "SchedulerModule"; }
|
std::string getType() const override { return "SchedulerModule"; }
|
||||||
bool isIdle() const override { return true; }
|
bool isIdle() const override { return true; }
|
||||||
int getVersion() const override { return 1; }
|
int getVersion() const override { return 1; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Configuration
|
// Configuration
|
||||||
int m_hyperfocusThresholdMinutes = 120; // 2 heures par défaut
|
int m_hyperfocusThresholdMinutes = 120; // 2 heures par défaut
|
||||||
int m_breakReminderIntervalMinutes = 45; // Rappel toutes les 45 min
|
int m_breakReminderIntervalMinutes = 45; // Rappel toutes les 45 min
|
||||||
int m_breakDurationMinutes = 10; // Pause de 10 min suggérée
|
int m_breakDurationMinutes = 10; // Pause de 10 min suggérée
|
||||||
|
|
||||||
// État
|
// État
|
||||||
std::vector<Task> m_tasks;
|
std::vector<Task> m_tasks;
|
||||||
std::string m_currentTaskId;
|
std::string m_currentTaskId;
|
||||||
float m_sessionStartTime = 0.0f; // Temps de début de session (gameTime)
|
float m_sessionStartTime = 0.0f; // Temps de début de session (gameTime)
|
||||||
float m_lastBreakTime = 0.0f; // Dernier rappel de pause
|
float m_lastBreakTime = 0.0f; // Dernier rappel de pause
|
||||||
float m_lastActivityTime = 0.0f; // Dernière activité utilisateur
|
float m_lastActivityTime = 0.0f; // Dernière activité utilisateur
|
||||||
bool m_hyperfocusAlertSent = false;
|
bool m_hyperfocusAlertSent = false;
|
||||||
int m_totalFocusMinutesToday = 0;
|
int m_totalFocusMinutesToday = 0;
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
grove::IIO* m_io = nullptr;
|
grove::IIO* m_io = nullptr;
|
||||||
std::unique_ptr<grove::JsonDataNode> m_config;
|
std::unique_ptr<grove::JsonDataNode> m_config;
|
||||||
std::shared_ptr<spdlog::logger> m_logger;
|
std::shared_ptr<spdlog::logger> m_logger;
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
void checkHyperfocus(float currentTime);
|
void checkHyperfocus(float currentTime);
|
||||||
void checkBreakReminder(float currentTime);
|
void checkBreakReminder(float currentTime);
|
||||||
void startTask(const std::string& taskId);
|
void startTask(const std::string& taskId);
|
||||||
void completeCurrentTask();
|
void completeCurrentTask();
|
||||||
Task* findTask(const std::string& taskId);
|
Task* findTask(const std::string& taskId);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace aissia
|
} // namespace aissia
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
grove::IModule* createModule();
|
grove::IModule* createModule();
|
||||||
void destroyModule(grove::IModule* module);
|
void destroyModule(grove::IModule* module);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user