#include #include #include #include "services/LLMService.hpp" #include "services/StorageService.hpp" #include "services/PlatformService.hpp" #include "services/VoiceService.hpp" #include #include #include #include #include #include #include #include namespace fs = std::filesystem; // Helper to clone IDataNode (workaround for missing clone() method) std::unique_ptr cloneDataNode(grove::IDataNode* node) { if (!node) return nullptr; // Cast to JsonDataNode to access the JSON data auto* jsonNode = dynamic_cast(node); if (jsonNode) { return std::make_unique( jsonNode->getName(), jsonNode->getJsonData() ); } // Fallback: create empty node return std::make_unique("data"); } // Global flag for clean shutdown static volatile bool g_running = true; void signalHandler(int signal) { spdlog::info("Signal {} recu, arret en cours...", signal); g_running = false; } // Simple file watcher for hot-reload class FileWatcher { public: void watch(const std::string& path) { if (fs::exists(path)) { m_lastModified[path] = fs::last_write_time(path); } } bool hasChanged(const std::string& path) { if (!fs::exists(path)) return false; auto currentTime = fs::last_write_time(path); auto it = m_lastModified.find(path); if (it == m_lastModified.end()) { m_lastModified[path] = currentTime; return false; } if (currentTime != it->second) { it->second = currentTime; return true; } return false; } private: std::unordered_map m_lastModified; }; // Load JSON config file std::unique_ptr loadConfig(const std::string& path) { if (fs::exists(path)) { std::ifstream file(path); nlohmann::json j; file >> j; auto config = std::make_unique("config", j); spdlog::info("Config chargee: {}", path); return config; } else { spdlog::warn("Config non trouvee: {}, utilisation des defauts", path); return std::make_unique("config"); } } // Module entry in our simple manager struct ModuleEntry { std::string name; std::string configFile; std::string path; std::unique_ptr loader; std::unique_ptr io; grove::IModule* module = nullptr; }; // Message router between modules and services class MessageRouter { public: void addModuleIO(const std::string& name, grove::IIO* io) { m_moduleIOs[name] = io; } void addServiceIO(const std::string& name, grove::IIO* io) { m_serviceIOs[name] = io; } void routeMessages() { // Collect all messages from modules and services std::vector messages; for (auto& [name, io] : m_moduleIOs) { if (!io) continue; while (io->hasMessages() > 0) { messages.push_back(io->pullMessage()); } } for (auto& [name, io] : m_serviceIOs) { if (!io) continue; while (io->hasMessages() > 0) { messages.push_back(io->pullMessage()); } } // Route messages to appropriate destinations for (auto& msg : messages) { // Determine destination based on topic prefix std::string prefix = msg.topic.substr(0, msg.topic.find(':')); // Route to services if (prefix == "llm" || prefix == "storage" || prefix == "platform" || prefix == "voice") { for (auto& [name, io] : m_serviceIOs) { if (io && msg.data) { io->publish(msg.topic, cloneDataNode(msg.data.get())); } } } // Route to modules (broadcast) for (auto& [name, io] : m_moduleIOs) { if (io && msg.data) { io->publish(msg.topic, cloneDataNode(msg.data.get())); } } } } private: std::map m_moduleIOs; std::map m_serviceIOs; }; int main(int argc, char* argv[]) { // Setup logging auto console = spdlog::stdout_color_mt("Aissia"); spdlog::set_default_logger(console); spdlog::set_level(spdlog::level::debug); spdlog::set_pattern("[%H:%M:%S.%e] [%n] [%^%l%$] %v"); spdlog::info("========================================"); spdlog::info(" AISSIA - Assistant Personnel IA"); spdlog::info(" Powered by GroveEngine"); spdlog::info(" Architecture: Services + Hot-Reload Modules"); spdlog::info("========================================"); // Signal handling std::signal(SIGINT, signalHandler); std::signal(SIGTERM, signalHandler); // Paths const std::string modulesDir = "./modules/"; const std::string configDir = "./config/"; // ========================================================================= // Infrastructure Services (non hot-reloadable) // ========================================================================= spdlog::info("Initialisation des services infrastructure..."); // Create IIO for services auto llmIO = grove::IOFactory::create("intra", "LLMService"); auto storageIO = grove::IOFactory::create("intra", "StorageService"); auto platformIO = grove::IOFactory::create("intra", "PlatformService"); auto voiceIO = grove::IOFactory::create("intra", "VoiceService"); // LLM Service aissia::LLMService llmService; llmService.initialize(llmIO.get()); llmService.loadConfig(configDir + "ai.json"); // Register default tools llmService.registerTool( "get_current_time", "Obtient l'heure actuelle", {{"type", "object"}, {"properties", nlohmann::json::object()}}, [](const nlohmann::json& input) -> nlohmann::json { std::time_t now = std::time(nullptr); std::tm* tm = std::localtime(&now); char buffer[64]; std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm); return {{"time", buffer}}; } ); // Storage Service aissia::StorageService storageService; storageService.initialize(storageIO.get()); { auto storageConfig = loadConfig(configDir + "storage.json"); std::string dbPath = storageConfig->getString("database_path", "./data/aissia.db"); std::string journalMode = storageConfig->getString("journal_mode", "WAL"); int busyTimeout = storageConfig->getInt("busy_timeout_ms", 5000); storageService.openDatabase(dbPath, journalMode, busyTimeout); } // Platform Service aissia::PlatformService platformService; platformService.initialize(platformIO.get()); { auto monitorConfig = loadConfig(configDir + "monitoring.json"); int pollInterval = monitorConfig->getInt("poll_interval_ms", 1000); int idleThreshold = monitorConfig->getInt("idle_threshold_seconds", 300); platformService.configure(pollInterval, idleThreshold); } // Voice Service aissia::VoiceService voiceService; voiceService.initialize(voiceIO.get()); { auto voiceConfig = loadConfig(configDir + "voice.json"); auto* ttsNode = voiceConfig->getChildReadOnly("tts"); if (ttsNode) { bool enabled = ttsNode->getBool("enabled", true); int rate = ttsNode->getInt("rate", 0); int volume = ttsNode->getInt("volume", 80); voiceService.configureTTS(enabled, rate, volume); } auto* sttNode = voiceConfig->getChildReadOnly("stt"); if (sttNode) { bool enabled = sttNode->getBool("enabled", true); std::string language = sttNode->getString("language", "fr"); std::string apiKeyEnv = sttNode->getString("api_key_env", "OPENAI_API_KEY"); const char* apiKey = std::getenv(apiKeyEnv.c_str()); voiceService.configureSTT(enabled, language, apiKey ? apiKey : ""); } } spdlog::info("Services initialises: LLM={}, Storage={}, Platform={}, Voice={}", llmService.isHealthy() ? "OK" : "FAIL", storageService.isHealthy() ? "OK" : "FAIL", platformService.isHealthy() ? "OK" : "FAIL", voiceService.isHealthy() ? "OK" : "FAIL"); // ========================================================================= // Hot-Reloadable Modules // ========================================================================= std::map modules; FileWatcher watcher; MessageRouter router; // Register service IOs with router router.addServiceIO("LLMService", llmIO.get()); router.addServiceIO("StorageService", storageIO.get()); router.addServiceIO("PlatformService", platformIO.get()); router.addServiceIO("VoiceService", voiceIO.get()); // Liste des modules a charger (sans infrastructure) std::vector> moduleList = { {"SchedulerModule", "scheduler.json"}, {"NotificationModule", "notification.json"}, {"MonitoringModule", "monitoring.json"}, {"AIModule", "ai.json"}, {"VoiceModule", "voice.json"}, {"StorageModule", "storage.json"}, }; // Charger les modules for (const auto& [moduleName, configFile] : moduleList) { std::string modulePath = modulesDir + "lib" + moduleName + ".so"; if (!fs::exists(modulePath)) { spdlog::warn("{} non trouve: {}", moduleName, modulePath); continue; } ModuleEntry entry; entry.name = moduleName; entry.configFile = configFile; entry.path = modulePath; entry.loader = std::make_unique(); entry.io = grove::IOFactory::create("intra", moduleName); auto modulePtr = entry.loader->load(modulePath, moduleName); if (!modulePtr) { spdlog::error("Echec du chargement: {}", moduleName); continue; } // Configure auto config = loadConfig(configDir + configFile); modulePtr->setConfiguration(*config, entry.io.get(), nullptr); entry.module = modulePtr.release(); watcher.watch(modulePath); // Register with router router.addModuleIO(moduleName, entry.io.get()); spdlog::info("{} charge et configure", moduleName); modules[moduleName] = std::move(entry); } if (modules.empty()) { spdlog::error("Aucun module charge! Build les modules: cmake --build build --target modules"); return 1; } // ========================================================================= // Main Loop // ========================================================================= spdlog::info("Demarrage de la boucle principale (Ctrl+C pour quitter)"); using Clock = std::chrono::high_resolution_clock; auto startTime = Clock::now(); auto lastFrame = Clock::now(); const auto targetFrameTime = std::chrono::milliseconds(100); // 10 Hz grove::JsonDataNode frameInput("frame"); uint64_t frameCount = 0; while (g_running) { auto frameStart = Clock::now(); // Calculate times auto deltaTime = std::chrono::duration(frameStart - lastFrame).count(); auto gameTime = std::chrono::duration(frameStart - startTime).count(); lastFrame = frameStart; // Prepare frame input frameInput.setDouble("deltaTime", deltaTime); frameInput.setInt("frameCount", frameCount); frameInput.setDouble("gameTime", gameTime); // ===================================================================== // Hot-Reload Check (every 10 frames = ~1 second) // ===================================================================== if (frameCount % 10 == 0) { for (auto& [name, entry] : modules) { if (watcher.hasChanged(entry.path)) { spdlog::info("Modification detectee: {}, hot-reload...", name); // Get state before reload std::unique_ptr state; if (entry.module) { state = entry.module->getState(); } // Reload auto reloaded = entry.loader->load(entry.path, name, true); if (reloaded) { auto config = loadConfig(configDir + entry.configFile); reloaded->setConfiguration(*config, entry.io.get(), nullptr); if (state) { reloaded->setState(*state); } entry.module = reloaded.release(); spdlog::info("{} recharge avec succes!", name); } } } } // ===================================================================== // Process Services (non hot-reloadable infrastructure) // ===================================================================== llmService.process(); storageService.process(); platformService.process(); voiceService.process(); // ===================================================================== // Process Modules (hot-reloadable) // ===================================================================== for (auto& [name, entry] : modules) { if (entry.module) { entry.module->process(frameInput); } } // ===================================================================== // Route messages between modules and services // ===================================================================== router.routeMessages(); // ===================================================================== // Frame timing // ===================================================================== frameCount++; auto frameEnd = Clock::now(); auto frameDuration = frameEnd - frameStart; if (frameDuration < targetFrameTime) { std::this_thread::sleep_for(targetFrameTime - frameDuration); } // Status log every 30 seconds if (frameCount % 300 == 0) { int minutes = static_cast(gameTime / 60.0f); int seconds = static_cast(gameTime) % 60; spdlog::debug("Session: {}m{}s, {} modules actifs, 4 services", minutes, seconds, modules.size()); } } // ========================================================================= // Shutdown // ========================================================================= spdlog::info("Arret en cours..."); // Shutdown modules first for (auto& [name, entry] : modules) { if (entry.module) { entry.module->shutdown(); spdlog::info("{} arrete", name); } } // Shutdown services voiceService.shutdown(); platformService.shutdown(); storageService.shutdown(); llmService.shutdown(); spdlog::info("A bientot!"); return 0; }