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>
52 lines
2.0 KiB
Dart
52 lines
2.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:manager_api_new/api.dart';
|
|
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
|
import 'package:mymuseum_visitapp/Services/assistantService.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/llm_client.dart';
|
|
|
|
/// Client LLM via backend MyInfoMate (.NET + Gemini Flash).
|
|
///
|
|
/// Utilise AppType.Voice pour signaler au backend :
|
|
/// - Pas de navigation UI (pas de navigate_to_section / navigate_to_configuration)
|
|
/// - Pas de show_cards
|
|
/// - Prompt audio-friendly : réponses courtes, sans markdown, max 30s à l'oral
|
|
class MyInfoMateLlmClient implements LlmClient {
|
|
final VisitAppContext visitAppContext;
|
|
|
|
/// ⚠️ La conversation est **partagée** avec le chat écrit et le mode proactif —
|
|
/// une conversation, plusieurs surfaces. Ce client construisait la sienne
|
|
/// (`maxHistory: 6`), si bien que poser une question aux lunettes puis ouvrir le
|
|
/// chat donnait un guide qui ne savait rien de ce qui venait d'être demandé.
|
|
AssistantService get _service => visitAppContext.assistant;
|
|
|
|
MyInfoMateLlmClient({required this.visitAppContext});
|
|
|
|
@override
|
|
Future<({String reply, bool expectsReply})> chat(
|
|
String message, {
|
|
String? configurationId,
|
|
String languageCode = 'FR',
|
|
}) async {
|
|
final cfgId = configurationId ?? visitAppContext.configuration?.id;
|
|
|
|
debugPrint('[MyInfoMateLlmClient] chat Voice — instanceId=${visitAppContext.instanceId} '
|
|
'configId=$cfgId lang=$languageCode apiKey=${visitAppContext.apiKey != null ? "set" : "null"}');
|
|
|
|
try {
|
|
final response = await _service.chatWithAppType(
|
|
message: message,
|
|
configurationId: cfgId,
|
|
appType: AppType.Mobile,
|
|
isVoice: true,
|
|
);
|
|
return (reply: response.reply, expectsReply: response.expectsReply);
|
|
} catch (e) {
|
|
debugPrint('[MyInfoMateLlmClient] error: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void clearHistory() => _service.clearHistory();
|
|
}
|