aissia/src/services/STTService.cpp
StillHammer 3915424d75 feat(wip): Phase 7.1 STT Service Layer - Architecture complète (ne compile pas)
Architecture Phase 7 STT implémentée mais bloquée par conflits de macros
entre GroveEngine (JsonDataNode.h) et spdlog/fmt.

## Nouveau contenu

### Interfaces & Services
- ISTTService.hpp: Interface service STT (modes passive/active, callbacks)
- STTService.{hpp,cpp}: Implémentation service STT avec factory pattern
- VoskSTTEngine.{hpp,cpp}: Engine STT local Vosk (~50MB model)

### Factory Pattern
- STTEngineFactory: Support multi-engines (Vosk, Whisper API, auto-select)
- Fallback automatique Vosk -> Whisper API

### Configuration
- config/voice.json: Config Phase 7 (passive_mode, active_mode, whisper_api)
- Support modèles Vosk locaux + fallback cloud

### Intégration
- VoiceService: Nouvelle méthode configureSTT(json) pour Phase 7
- main.cpp: Chargement config STT depuis voice.json
- CMakeLists.txt: Ajout fichiers + dépendance optionnelle Vosk

## Problème de Compilation

**Bloqué par conflits de macros**:
- JsonDataNode.h (GroveEngine) définit des macros qui polluent 'logger' et 'queue'
- Cause erreurs dans VoiceService.cpp et STTService.cpp
- Voir plans/PHASE7_COMPILATION_ISSUE.md pour diagnostic complet

## Fonctionnalités Implémentées

 Architecture STT complète (service layer + engines)
 Support Vosk local (modèles français)
 Factory pattern avec auto-selection
 Configuration JSON Phase 7
 Callbacks transcription/keywords
 Ne compile pas (macro conflicts)

## Prochaines Étapes

1. Résoudre conflits macros (fixer GroveEngine ou isolation namespace)
2. Phase 7.2: PocketSphinxEngine (keyword spotting "Celuna")
3. Tests intégration STT

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 09:01:26 +08:00

190 lines
4.7 KiB
C++

// CRITICAL ORDER: Include system headers first
#include <nlohmann/json.hpp>
#include <cstdlib>
#include <memory>
#include <string>
// Include local headers before spdlog
#include "STTService.hpp"
#include "../shared/audio/ISTTEngine.hpp"
// Include spdlog after local headers
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
namespace aissia {
STTService::STTService(const nlohmann::json& config)
: m_config(config)
{
m_logger = spdlog::get("STTService");
if (!m_logger) {
m_logger = spdlog::stdout_color_mt("STTService");
}
// Extract language from config
if (config.contains("active_mode") && config["active_mode"].contains("language")) {
m_language = config["active_mode"]["language"].get<std::string>();
}
m_logger->info("STTService created");
}
STTService::~STTService() {
stop();
}
bool STTService::start() {
m_logger->info("Starting STT service");
loadEngines();
if (!m_activeEngine || !m_activeEngine->isAvailable()) {
m_logger->error("No active STT engine available");
return false;
}
m_logger->info("STT service started");
return true;
}
void STTService::stop() {
m_logger->info("Stopping STT service");
stopListening();
m_activeEngine.reset();
}
void STTService::setMode(STTMode mode) {
if (m_currentMode == mode) {
return;
}
m_logger->info("Switching STT mode");
m_currentMode = mode;
}
std::string STTService::transcribeFile(const std::string& filePath) {
if (!m_activeEngine || !m_activeEngine->isAvailable()) {
m_logger->warn("No STT engine available for transcription");
return "";
}
m_logger->info("Transcribing file");
try {
std::string result = m_activeEngine->transcribeFile(filePath);
m_logger->info("Transcription complete");
return result;
} catch (const std::exception& e) {
m_logger->error("Transcription failed");
return "";
}
}
std::string STTService::transcribe(const std::vector<float>& audioData) {
if (!m_activeEngine || !m_activeEngine->isAvailable()) {
return "";
}
if (audioData.empty()) {
return "";
}
try {
std::string result = m_activeEngine->transcribe(audioData);
if (!result.empty() && m_listening && m_onTranscription) {
m_onTranscription(result, m_currentMode);
}
return result;
} catch (const std::exception& e) {
m_logger->error("Transcription failed");
return "";
}
}
void STTService::startListening(TranscriptionCallback onTranscription,
KeywordCallback onKeyword) {
m_logger->info("Start listening");
m_onTranscription = onTranscription;
m_onKeyword = onKeyword;
m_listening = true;
m_logger->warn("Streaming microphone capture not yet implemented");
}
void STTService::stopListening() {
if (!m_listening) {
return;
}
m_logger->info("Stop listening");
m_listening = false;
}
void STTService::setLanguage(const std::string& language) {
m_logger->info("Setting language");
m_language = language;
if (m_activeEngine) {
m_activeEngine->setLanguage(language);
}
}
bool STTService::isAvailable() const {
return m_activeEngine && m_activeEngine->isAvailable();
}
std::string STTService::getCurrentEngine() const {
if (m_activeEngine) {
return m_activeEngine->getEngineName();
}
return "none";
}
void STTService::loadEngines() {
m_logger->info("Loading STT engines");
std::string engineType = "auto";
if (m_config.contains("active_mode")) {
const auto& activeMode = m_config["active_mode"];
if (activeMode.contains("engine")) {
engineType = activeMode["engine"];
}
}
std::string modelPath;
if (m_config.contains("active_mode")) {
const auto& activeMode = m_config["active_mode"];
if (activeMode.contains("model_path")) {
modelPath = activeMode["model_path"];
}
}
std::string apiKey;
if (m_config.contains("whisper_api")) {
const auto& whisperApi = m_config["whisper_api"];
std::string apiKeyEnv = "OPENAI_API_KEY";
if (whisperApi.contains("api_key_env")) {
apiKeyEnv = whisperApi["api_key_env"];
}
const char* envVal = std::getenv(apiKeyEnv.c_str());
if (envVal) {
apiKey = envVal;
}
}
m_activeEngine = STTEngineFactory::create(engineType, modelPath, apiKey);
if (m_activeEngine && m_activeEngine->isAvailable()) {
m_activeEngine->setLanguage(m_language);
m_logger->info("STT engine loaded successfully");
} else {
m_logger->warn("No active STT engine available");
}
}
} // namespace aissia