mymuseum-visitapp/lib/Helpers/assistantSuggestions.dart
Thomas Fransolet af44927d4a Assistant IA aligné sur le web + parcours guidés + centrage de carte
Assistant
  AssistantChatSheet.dart (+252), Helpers/assistantSuggestions.dart,
  Services/assistantService.dart — même comportement et mêmes suggestions que
  visitapp-web, dérivées du contenu réel.

Parcours guidés
  guided_path_content_progression_page (+72), guided_path_map_progression_page (+39),
  parcours_page : alignés sur les 3 questions de progression qui remplacent les
  9 booléens côté manager-app.

Carte
  Helpers/mapCenter.dart + les trois vues (flutter_map, google_map, map_box) et
  marker_view : centrage et icônes honorés.

Fix du build
  guided_step_challenge.dart:83 — typage explicite. Avec le // @dart=2.18 de
  manager_api_new côté manager-app, c'est ce qui débloque flutter build apk.

flutter build apk .
Reste ouvert : M3 — meterZoneGPS toujours ignoré, une constante en dur à 100 m
remplace le rayon configuré par section.
2026-08-09 22:15:26 +02:00

121 lines
4.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:mymuseum_visitapp/Models/visitContext.dart';
/// Questions proposées au visiteur quand il ouvre l'assistant.
///
/// Dérivées du contenu réel de la configuration : les sections sont déjà en
/// mémoire (`VisitAppContext.currentSections`, alimenté au chargement de la
/// configuration) et leurs titres déjà traduits par le CMS. Un lieu sans agenda
/// ne propose donc pas de question sur l'agenda.
///
/// Miroir de `visitapp-web/src/lib/assistantSuggestions.ts` — garder les deux
/// listes de phrases alignées.
class AssistantSuggestions {
static const int _max = 3;
static const Map<String, Map<String, String>> _phrases = {
'aboutSection': {
'FR': 'Parlez-moi de « {name} »',
'NL': 'Vertel me over "{name}"',
'EN': 'Tell me about "{name}"',
'DE': 'Erzählen Sie mir von „{name}"',
'ES': 'Háblame de «{name}»',
'IT': 'Parlami di "{name}"',
},
'whatsOn': {
'FR': "Qu'est-ce qu'il y a cette semaine ?",
'NL': 'Wat is er deze week te doen?',
'EN': "What's on this week?",
'DE': 'Was gibt es diese Woche?',
'ES': '¿Qué hay esta semana?',
'IT': 'Cosa cè questa settimana?',
},
'withKids': {
'FR': 'Que faire avec des enfants ?',
'NL': 'Wat kunnen we doen met kinderen?',
'EN': 'What can we do with children?',
'DE': 'Was können wir mit Kindern machen?',
'ES': '¿Qué hacer con niños?',
'IT': 'Cosa fare con i bambini?',
},
'highlights': {
'FR': "Qu'est-ce qu'il ne faut pas manquer ?",
'NL': 'Wat mag ik niet missen?',
'EN': "What shouldn't I miss?",
'DE': 'Was sollte ich nicht verpassen?',
'ES': '¿Qué no me puedo perder?',
'IT': 'Cosa non devo perdere?',
},
'inOneHour': {
'FR': "Que voir si je n'ai qu'une heure ?",
'NL': 'Wat zie ik als ik maar één uur heb?',
'EN': 'What should I see in one hour?',
'DE': 'Was sollte ich in einer Stunde sehen?',
'ES': '¿Qué ver si solo tengo una hora?',
'IT': 'Cosa vedere se ho solo unora?',
},
'openingHours': {
'FR': 'Quels sont les horaires ?',
'NL': 'Wat zijn de openingstijden?',
'EN': 'What are the opening hours?',
'DE': 'Wie sind die Öffnungszeiten?',
'ES': '¿Cuál es el horario?',
'IT': 'Quali sono gli orari?',
},
};
static String _phrase(String key, String lang, [String? name]) {
final table = _phrases[key]!;
final template = table[lang] ?? table['FR']!;
return name == null ? template : template.replaceAll('{name}', name);
}
static String _stripHtml(String value) =>
value.replaceAll(RegExp(r'<[^>]*>'), '').trim();
static String _titleOf(Map<String, dynamic> section, String lang) {
final titles = section['title'];
if (titles is! List || titles.isEmpty) return '';
final match = titles.firstWhere(
(t) => t is Map && t['language'] == lang && (t['value'] as String?)?.isNotEmpty == true,
orElse: () => titles.first,
);
if (match is! Map) return '';
return _stripHtml((match['value'] as String?) ?? '');
}
static List<String> build(VisitAppContext context, {String? currentSectionId}) {
final lang = (context.language ?? 'FR').toUpperCase();
final sections = (context.currentSections ?? [])
.whereType<Map<String, dynamic>>()
.where((s) => s['isActive'] != false && s['isSubSection'] != true)
.toList();
bool has(String type) => sections.any((s) => s['type'] == type);
final suggestions = <String>[];
// La section ouverte passe en premier : c'est ce que le visiteur a sous les yeux.
if (currentSectionId != null) {
final current = sections.firstWhere(
(s) => s['id'] == currentSectionId,
orElse: () => <String, dynamic>{},
);
if (current.isNotEmpty) {
final name = _titleOf(current, lang);
if (name.isNotEmpty) suggestions.add(_phrase('aboutSection', lang, name));
}
}
if (has('Agenda')) suggestions.add(_phrase('whatsOn', lang));
if (has('Parcours') || has('Game')) suggestions.add(_phrase('withKids', lang));
// Génériques — trois, pour qu'un lieu minimal ait quand même le compte complet.
suggestions.add(_phrase('highlights', lang));
suggestions.add(_phrase('inOneHour', lang));
suggestions.add(_phrase('openingHours', lang));
return suggestions.toSet().take(_max).toList();
}
}