mymuseum-visitapp/lib/constants.dart
Thomas Fransolet e9e68942c9 Proactif câblé, M3 fermé, voix du CMS honorée, et trois services morts supprimés
Déclenchement proactif (lot F) — quatre branchements : la garde, le cycle de
vie accroché à VoiceController, les points GPS peuplés depuis la configuration,
et un réglage visiteur.

⚠️ La garde recommandée par le plan aurait coûté de l'argent. _trigger appelle
le LLM d'abord et ne parle qu'ensuite via activeVoiceOrchestrator?.ttsEngine,
dont le ?. avale le cas « aucun mode vocal actif ». Avec proactiveModeEnabled
seul, un visiteur traversant une zone avec l'app en poche et le vocal éteint
consommait des jetons Gemini facturés au client pour une phrase que personne
n'entend. La garde interroge l'orchestrateur : la vraie condition n'est pas
« quel matériel » mais « y a-t-il quelqu'un pour écouter ».

Il n'y avait pas de troisième mode à inventer : VoiceMode.voiceOnly existe déjà
et construit le même orchestrateur que le mode lunettes. Le proactif est donc un
sous-réglage, pas une tuile. glassesEnabled est supprimé, ses trois usages
étaient morts — dont une pastille « Lunettes / Déconnecté » jamais affichée.

M3 — meterZoneGPS devient le rayon par section, la constante n'étant plus qu'un
défaut. Deux pièges de format vérifiés : currentSections porte des maps JSON
brutes, pas des DTO, et latitude/longitude y sont des chaînes.

Voix du guide — le sélecteur Viva/Marco de manager-app écrivait dans le vide :
Instance.GuideVoiceId n'était lu nulle part ici, l'app utilisant une constante
de build dont le défaut était Algieba, une voix que personne n'a écoutée. Même
forme que W1. Et aucun APK ne parlait avec la voix du produit : GeminiTtsEngine
n'est choisi que si GEMINI_API_KEY est injectée, ce qui n'était fait nulle part,
donc repli silencieux sur la voix système. Le repli reste le défaut — les tests
ne coûtent alors ni jetons ni argent — mais il s'annonce dans les logs.

Code mort supprimé : wake_word_service.dart, glasses_qr_scanner_service.dart et
glasses_tts_service.dart, les ancêtres de Services/Glasses/. Ils portaient les 4
erreurs kElevenLabs* et l'APK se construisait quand même, personne ne les
important. flutter analyze lib rend désormais zéro erreur.
⚠️ android/.../WakeWordService.kt est un homonyme bien vivant, non touché.
impl/elevenlabs_tts_engine.dart est conservé comme option.

flutter build apk --debug --flavor dev : vert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 11:57:44 +02:00

148 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
// Third-party integrations — injectées via --dart-define, jamais committées.
//
// ⚠️ GEMINI_API_KEY absente = repli sur la voix système du téléphone, et c'est
// **le comportement par défaut de tous les builds**. Utile pour tester sans dépenser de
// jetons, trompeur si on l'ignore : jusqu'au 2026-08-13 le repli était silencieux et
// personne ne savait que les APK ne parlaient pas avec la voix du produit. Il se signale
// désormais dans les logs (`[VoiceController] GEMINI_API_KEY absente…`).
//
// Pour entendre la vraie voix :
// flutter run --flavor dev --dart-define=GEMINI_API_KEY=xxxx
// flutter build apk --flavor mdlf --dart-define=GEMINI_API_KEY=xxxx --dart-define=INSTANCE_ID=... --dart-define=API_BASE_URL=... --dart-define=API_KEY=...
//
// Whisper STT (OpenAI ou serveur auto-hébergé compatible) — clé vide = STT du device.
const kWhisperApiKey = String.fromEnvironment('WHISPER_API_KEY', defaultValue: '');
const kWhisperEndpoint = String.fromEnvironment('WHISPER_ENDPOINT',
defaultValue: 'https://api.openai.com/v1/audio/transcriptions');
// Gemini TTS (2.5 Flash)
const kGeminiApiKey = String.fromEnvironment('GEMINI_API_KEY', defaultValue: '');
// ⚠️ Repli seulement. La voix réelle vient du CMS — `Instance.guideVoiceId`, réglée par le
// client dans manager-app (sélecteur Viva/Marco). Le défaut valait `Algieba`, une voix que
// personne n'a validée : il date d'avant le sélecteur, et rien ne lisait le choix du client.
// Sulafat = Viva (féminine), écoutée et validée le 2026-08-07 ; Umbriel = Marco (masculine).
const kGeminiTtsVoice = String.fromEnvironment('GEMINI_TTS_VOICE', defaultValue: 'Sulafat');
const kGeminiTtsPrompt = String.fromEnvironment('GEMINI_TTS_PROMPT',
defaultValue: 'Voix de guide de musée, ton chaleureux et bienveillant, rythme posé et clair.');
// ElevenLabs retiré du pipeline (trop cher). Fichier impl/elevenlabs_tts_engine.dart
// conservé comme référence mais non utilisé.
const kPicovoiceAccessKey = String.fromEnvironment('PICOVOICE_ACCESS_KEY', defaultValue: '');
// API configuration — injectées au build via --dart-define
// Ex: flutter build appbundle --flavor mdlf --dart-define=INSTANCE_ID=65ccc67265373befd15be511 --dart-define=API_BASE_URL=https://api.mymuseum.be --dart-define=API_KEY=xxxx
const kApiBaseUrl = String.fromEnvironment('API_BASE_URL',
defaultValue: 'http://192.168.31.228:5000');
const kApiKey = String.fromEnvironment('API_KEY', defaultValue: '');
const kInstanceId = String.fromEnvironment('INSTANCE_ID',
defaultValue: '63514fd67ed8c735aaa4b8f2');
// Flavor injecté au build via --dart-define=FLAVOR=mdlf
const _flavor = String.fromEnvironment('FLAVOR', defaultValue: 'dev');
// Colors — définies par flavor
// Pour modifier les couleurs d'un client : changer les valeurs hex ci-dessous
// puis rebuilder avec --dart-define=FLAVOR=nomduclient
// Couleurs identiques pour tous les clients actuels
const kBackgroundColor = Color(0xFFFFFFFF);
const kMainColor = Color(0xFF306bac);
const kSecondColor = Color(0xFF309cb0);
const kConfigurationColor = Color(0xFF2F4858);
/*const kArticleTitleSize = 25.0;
const kArticleDescriptionSize = 12.5;*/
const kArticleContentSize = 16.0;
const kArticleContentBiggerSize = 24.0;
const List<String> languages = [
"FR",
"NL",
"EN",
"DE",
"IT",
"ES",
"PL",
"CN",
"AR",
"UK"
]; // hmmmm depends on config..
const String defaultLanguage = "EN";
const kDefaultPadding = 20.0;
const kMainGrey = Color(0xFF424242);
const kSecondGrey = Color(0xFF555457);
//const kSecondRed = Color(0xFF622727);
const kTextRed = Color(0xFFba0505);
const kBackgroundGrey = Color(0xFFb5b7b9);
const kBackgroundSecondGrey = Color(0xFF5b5b63);
const kMainColor0 = Color(_flavor == 'mdlf'
? 0xFFe52122
: // rouge
_flavor == 'fortsaintheribert'
? 0xFF306bac
: // bleu
0xFF306bac // test (défaut)
);
const kMainColor1 = Color(_flavor == 'mdlf'
? 0xFFed7082
: // rouge clair
_flavor == 'fortsaintheribert'
? 0xFF308aae
: // bleu
0xFF308aae // test (défaut)
);
const kMainColor2 = Color(_flavor == 'mdlf'
? 0xFFed7082
: // rouge clair
_flavor == 'fortsaintheribert'
? 0xFF309cb0
: // bleu
0xFF309cb0 // test (défaut)
);
const kSplashLogoAsset = _flavor == 'mdlf'
? 'assets/splash/mdlf.png'
: _flavor == 'fortsaintheribert'
? 'assets/splash/fortsaintheribert.png'
: 'assets/splash/dev.png';
const kLoaderAsset = _flavor == 'mdlf'
? 'assets/loader/mdlf.png'
: _flavor == 'fortsaintheribert'
? 'assets/loader/fortsaintheribert.png'
: 'assets/loader/dev.png';
const kBackgroundLight = Color(0xfff3f3f3);
const kGreen = Color(0xFF15bd5b);
const kTitleSize = 40.0;
const kDescriptionSize = 25.0;
const kSectionTitleDetailSize = 50.0;
const kSectionDescriptionDetailSize = 35.0;
const kMenuTitleDetailSize = 25.0;
const kMenuDescriptionDetailSize = 18.0;
const kNoneInfoOrIncorrect = 35.0;
// our default Shadow
const kDefaultShadow = BoxShadow(
offset: Offset(0, 15),
blurRadius: 27,
color: kBackgroundGrey, // Black color with 12% opacity
);
/*
const kTextStyle = TextStyle(
fontSize: 23,
color: kPrimaryColor,
fontWeight: FontWeight.w600,
);*/