aissia/src/main.cpp
StillHammer bc3b6cbaba feat: Implement Phase 1 complete - All 6 core modules
## New Modules
- StorageModule: SQLite persistence for sessions, app usage, conversations
- MonitoringModule: Cross-platform window tracking (Win32/X11)
- AIModule: Multi-provider LLM integration with agentic tool loop
- VoiceModule: TTS/STT coordination with speak queue

## Shared Libraries
- AissiaLLM: ILLMProvider abstraction (Claude + OpenAI providers)
- AissiaPlatform: IWindowTracker abstraction (Win32 + X11)
- AissiaAudio: ITTSEngine (SAPI/espeak) + ISTTEngine (Whisper API)
- HttpClient: Header-only HTTP client with OpenSSL

## Configuration
- Added JSON configs for all modules (storage, monitoring, ai, voice)
- Multi-provider LLM config with Claude and OpenAI support

## Dependencies
- SQLite3, OpenSSL, cpp-httplib (FetchContent)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 00:42:18 +08:00

264 lines
8.9 KiB
C++

#include <grove/ModuleLoader.h>
#include <grove/JsonDataNode.h>
#include <grove/IOFactory.h>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <chrono>
#include <thread>
#include <csignal>
#include <filesystem>
#include <fstream>
#include <map>
namespace fs = std::filesystem;
// Global flag for clean shutdown
static volatile bool g_running = true;
void signalHandler(int signal) {
spdlog::info("Signal {} reçu, arrêt 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<std::string, fs::file_time_type> m_lastModified;
};
// Load JSON config file
std::unique_ptr<grove::JsonDataNode> loadConfig(const std::string& path) {
if (fs::exists(path)) {
std::ifstream file(path);
nlohmann::json j;
file >> j;
auto config = std::make_unique<grove::JsonDataNode>("config", j);
spdlog::info("Config chargée: {}", path);
return config;
} else {
spdlog::warn("Config non trouvée: {}, utilisation des défauts", path);
return std::make_unique<grove::JsonDataNode>("config");
}
}
// Module entry in our simple manager
struct ModuleEntry {
std::string name;
std::string configFile;
std::string path;
std::unique_ptr<grove::ModuleLoader> loader;
std::unique_ptr<grove::IIO> io;
grove::IModule* module = nullptr;
};
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("========================================");
// Signal handling
std::signal(SIGINT, signalHandler);
std::signal(SIGTERM, signalHandler);
// Paths
const std::string modulesDir = "./modules/";
const std::string configDir = "./config/";
// =========================================================================
// Module Management
// =========================================================================
std::map<std::string, ModuleEntry> modules;
FileWatcher watcher;
// Liste des modules à charger
std::vector<std::pair<std::string, std::string>> moduleList = {
{"StorageModule", "storage.json"}, // Doit être chargé en premier (persistence)
{"SchedulerModule", "scheduler.json"},
{"NotificationModule", "notification.json"},
{"MonitoringModule", "monitoring.json"},
{"AIModule", "ai.json"},
{"VoiceModule", "voice.json"},
};
// Charger les modules
for (const auto& [moduleName, configFile] : moduleList) {
std::string modulePath = modulesDir + "lib" + moduleName + ".so";
if (!fs::exists(modulePath)) {
spdlog::warn("{} non trouvé: {}", moduleName, modulePath);
continue;
}
ModuleEntry entry;
entry.name = moduleName;
entry.configFile = configFile;
entry.path = modulePath;
entry.loader = std::make_unique<grove::ModuleLoader>();
entry.io = grove::IOFactory::create("intra", moduleName);
auto modulePtr = entry.loader->load(modulePath, moduleName);
if (!modulePtr) {
spdlog::error("Échec du chargement: {}", moduleName);
continue;
}
// Configure
auto config = loadConfig(configDir + configFile);
modulePtr->setConfiguration(*config, entry.io.get(), nullptr);
entry.module = modulePtr.release();
watcher.watch(modulePath);
spdlog::info("{} chargé et configuré", moduleName);
modules[moduleName] = std::move(entry);
}
if (modules.empty()) {
spdlog::error("Aucun module chargé! Build les modules: cmake --build build --target modules");
return 1;
}
// =========================================================================
// Main Loop
// =========================================================================
spdlog::info("Démarrage 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 pour un assistant
grove::JsonDataNode frameInput("frame");
uint64_t frameCount = 0;
while (g_running) {
auto frameStart = Clock::now();
// Calculate times
auto deltaTime = std::chrono::duration<float>(frameStart - lastFrame).count();
auto gameTime = std::chrono::duration<float>(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 détectée: {}, hot-reload...", name);
// Get state before reload
std::unique_ptr<grove::IDataNode> 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("{} rechargé avec succès!", name);
}
}
}
}
// =====================================================================
// Process all modules
// =====================================================================
for (auto& [name, entry] : modules) {
if (entry.module) {
entry.module->process(frameInput);
}
}
// Process IO messages
for (auto& [name, entry] : modules) {
while (entry.io && entry.io->hasMessages() > 0) {
auto msg = entry.io->pullMessage();
// Route messages between modules if needed
}
}
// =====================================================================
// 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<int>(gameTime / 60.0f);
int seconds = static_cast<int>(gameTime) % 60;
spdlog::debug("Session: {}m{}s, {} modules actifs",
minutes, seconds, modules.size());
}
}
// =========================================================================
// Shutdown
// =========================================================================
spdlog::info("Arrêt en cours...");
for (auto& [name, entry] : modules) {
if (entry.module) {
entry.module->shutdown();
spdlog::info("{} arrêté", name);
}
}
spdlog::info("À bientôt!");
return 0;
}