Le chat écrit, le vocal et le déclenchement proactif partagent désormais une seule conversation, portée par VisitAppContext.assistant. Les trois AssistantService séparés ont disparu : poser une question aux lunettes puis ouvrir le chat donnait un guide qui ne savait rien de ce qu'on venait de demander, et produisait deux lignes VisitorQuestion sans lien pour un visiteur qui avait simplement changé de surface. Le maxHistory du vocal (6) s'aligne sur 10 : deux surfaces qui partagent une conversation ne peuvent pas la tronquer différemment selon le point d'entrée. La concision vocale vient d'isVoice, qui change le prompt côté serveur. Le vrai obstacle n'était pas l'affichage mais la forme du chat : AssistantChatSheet gardait ses messages en List<Widget>, des bulles déjà construites. On ne rejoue pas une conversation à partir de widgets, et un tour vocal survenu pendant que la feuille était fermée n'aurait jamais pu y entrer. Le service porte maintenant les tours en données et notifie ses écouteurs. Deux listes, délibérément : celle envoyée au modèle et celle affichée ne coïncident pas. Un message d'erreur se montre sans repartir au guide, et le prompt d'un déclenchement proactif part au guide sans jamais s'afficher — seule sa réponse apparaît, marquée « À voix haute ». Sans ce marquage, le visiteur trouverait dans son chat des messages qu'il n'a jamais tapés. conversationId est enfin envoyé, et renouvelé quand la conversation est vidée : sinon la visite entière d'un visiteur n'en formerait qu'une. flutter analyze lib sans erreur, flutter build apk --debug --flavor dev vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
111 lines
4.6 KiB
Dart
111 lines
4.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:manager_api_new/api.dart';
|
|
import 'package:mymuseum_visitapp/Models/articleRead.dart';
|
|
import 'package:mymuseum_visitapp/Services/assistantService.dart';
|
|
import 'package:mymuseum_visitapp/Services/statisticsService.dart';
|
|
import 'package:mymuseum_visitapp/Models/beaconSection.dart';
|
|
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
|
|
import 'package:mymuseum_visitapp/client.dart';
|
|
import 'package:mymuseum_visitapp/constants.dart';
|
|
|
|
class VisitAppContext with ChangeNotifier {
|
|
Client clientAPI = Client(kApiBaseUrl);
|
|
|
|
String? id = "";
|
|
String? language = "";
|
|
String? instanceId = kInstanceId;
|
|
String? apiKey;
|
|
|
|
List<ConfigurationDTO>? configurations;
|
|
ConfigurationDTO? configuration;
|
|
List<String?>? sectionIds; // Use to valid QR code found
|
|
List<BeaconSection?>? beaconSections;
|
|
List<dynamic>? currentSections;
|
|
List<SectionRead> readSections = [];
|
|
bool isContentCurrentlyShown = false;
|
|
bool isScanningBeacons = false;
|
|
bool isScanBeaconAlreadyAllowed = false;
|
|
bool isMaximizeTextSize = false;
|
|
|
|
Size? puzzleSize;
|
|
|
|
List<ResourceModel> audiosNotWorking = [];
|
|
|
|
ApplicationInstanceDTO? applicationInstanceDTO; // null = assistant non activé
|
|
StatisticsService? statisticsService;
|
|
|
|
AssistantService? _assistant;
|
|
|
|
/// **La** conversation du visiteur avec le guide, partagée par le chat écrit, le
|
|
/// vocal et le déclenchement proactif. Créée à la première utilisation.
|
|
///
|
|
/// ⚠️ Chaque surface construisait auparavant la sienne : changer de surface donnait
|
|
/// un guide amnésique et coupait la conversation en deux côté serveur. Ne pas
|
|
/// réintroduire de `AssistantService(...)` ailleurs — c'est ce getter, ou rien.
|
|
AssistantService get assistant =>
|
|
_assistant ??= AssistantService(visitAppContext: this);
|
|
|
|
/// Retourne l'AppConfigurationLink de l'instance mobile pour la configuration courante.
|
|
/// Contient roundedValue, isSectionImageBackground, etc.
|
|
AppConfigurationLink? get currentAppConfigurationLink {
|
|
if (applicationInstanceDTO == null || configuration == null) return null;
|
|
return applicationInstanceDTO!.configurations
|
|
?.where((c) => c.configurationId == configuration!.id)
|
|
.firstOrNull;
|
|
}
|
|
|
|
bool? isAdmin = false;
|
|
bool? isAllLanguages = false;
|
|
bool notificationsEnabled = true;
|
|
|
|
/// Voix du guide choisie par le client dans manager-app (`Sulafat` = Viva, féminine ;
|
|
/// `Umbriel` = Marco, masculine). Null tant que l'instance n'a pas été lue.
|
|
///
|
|
/// ⚠️ C'est **le CMS qui décide**, pas le build : `kGeminiTtsVoice` n'est plus qu'un
|
|
/// repli. Le sélecteur existait dans manager-app et la colonne côté serveur depuis le
|
|
/// 07/08, mais rien ne les lisait ici — le choix du client était enregistré et ignoré.
|
|
String? guideVoiceId;
|
|
|
|
/// Mode proactif : l'assistant parle automatiquement à l'approche d'un beacon
|
|
/// ou d'une zone géographique, sans que le visiteur pose de question.
|
|
///
|
|
/// Indépendant du support : vaut pour les lunettes comme pour des écouteurs.
|
|
/// C'est un **sous-réglage** du mode vocal, pas un mode à part — il n'a d'effet
|
|
/// que lorsqu'un `VoiceMode` est actif, seul cas où il existe une sortie audio.
|
|
///
|
|
/// ⚠️ Volontairement **non persisté** (absent de `toMap`), comme `VoiceController.mode` :
|
|
/// un mode qui parle tout seul et se réactiverait en silence au lancement suivant
|
|
/// est une mauvaise surprise. Opt-in par session.
|
|
bool proactiveModeEnabled = false;
|
|
|
|
String? localPath;
|
|
|
|
VisitAppContext({this.language, this.id, this.configuration, this.isAdmin, this.isAllLanguages, this.instanceId, this.apiKey, this.notificationsEnabled = true});
|
|
|
|
Map<String, dynamic> toMap() {
|
|
return {
|
|
'id': id,
|
|
'instanceId': instanceId,
|
|
'language': language,
|
|
'isAdmin': isAdmin != null ? isAdmin! ? 1 : 0 : 0,
|
|
'isAllLanguages': isAllLanguages != null ? isAllLanguages! ? 1 : 0 : 0,
|
|
'apiKey': apiKey,
|
|
'notificationsEnabled': notificationsEnabled ? 1 : 0,
|
|
};
|
|
}
|
|
|
|
factory VisitAppContext.fromJson(Map<String, dynamic> json) {
|
|
return VisitAppContext(
|
|
id: json['id'] as String,
|
|
instanceId: json['instanceId'] as String,
|
|
language: json['language'] as String,
|
|
configuration: json['configuration'] == null ? null : ConfigurationDTO.fromJson(json['configuration']),
|
|
apiKey: json['apiKey'] as String?,
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return 'VisitAppContext{id: $id, instanceId: $instanceId, language: $language, configuration: $configuration, beaconSections: $beaconSections, isAdmin: $isAdmin}';
|
|
}
|
|
} |