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:
StillHammer 2025-11-25 22:13:16 +08:00
parent 439b55b176
commit 0dfb5f1535
13 changed files with 2015 additions and 2063 deletions

148
.gitignore vendored
View File

@ -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
View 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 |

View File

@ -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`)

View File

@ -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
View File

@ -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

View File

@ -1,6 +1,6 @@
{ {
"language": "fr", "language": "fr",
"silentMode": false, "silentMode": false,
"ttsEnabled": false, "ttsEnabled": false,
"maxQueueSize": 50 "maxQueueSize": 50
} }

View File

@ -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

View File

@ -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;
} }

View File

@ -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;
} }
} }

View File

@ -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);
} }

View File

@ -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;
} }
} }

View File

@ -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);
} }