diff --git a/lib/Screens/GuideIa/guide_ia_screen.dart b/lib/Screens/GuideIa/guide_ia_screen.dart index dc8a082..19a8e48 100644 --- a/lib/Screens/GuideIa/guide_ia_screen.dart +++ b/lib/Screens/GuideIa/guide_ia_screen.dart @@ -46,6 +46,9 @@ class _GuideIaScreenState extends State { /// Nul tant que le serveur n'a pas répondu : la ligne « ~N questions » ne s'affiche pas. int? _tokensPerQuestion; + /// Le client autorise-t-il l'enregistrement des questions de ses visiteurs ? (RGPD) + bool _collectionEnabled = true; + @override void initState() { super.initState(); @@ -85,6 +88,7 @@ class _GuideIaScreenState extends State { _nameController.text = instance?.guideName ?? ''; _personaController.text = instance?.guidePersonaPrompt ?? ''; _voiceId = instance?.guideVoiceId ?? kGuideVoiceViva; + _collectionEnabled = instance?.isVisitorQuestionCollectionEnabled ?? true; final ownLanguage = (instance?.guideFallbackMessages ?? []) .where((m) => m.language == _editingLanguage && (m.value ?? '').isNotEmpty); @@ -186,6 +190,7 @@ class _GuideIaScreenState extends State { instance.guideName = _nameController.text.trim(); instance.guidePersonaPrompt = _personaController.text.trim(); instance.guideVoiceId = _voiceId; + instance.isVisitorQuestionCollectionEnabled = _collectionEnabled; instance.guideFallbackMessages = [...otherLanguages, ...edited]; try { @@ -332,6 +337,8 @@ class _GuideIaScreenState extends State { const SizedBox(height: 16), _channelsCard(l), const SizedBox(height: 16), + _collectionCard(l), + const SizedBox(height: 16), _identityCard(l), ], ); @@ -398,6 +405,43 @@ class _GuideIaScreenState extends State { ); } + /// Interrupteur de collecte (RGPD, lot J). + /// + /// ⚠️ **C'est le client qui est responsable de traitement**, pas nous : il doit pouvoir + /// refuser. La collecte était inconditionnelle dès que l'assistant était actif. + /// + /// Le texte dit ce que couper coûte — sans quoi le gestionnaire le désactive « par + /// prudence » puis s'étonne que son onglet reste vide — et ce que couper ne fait pas : + /// les questions déjà enregistrées vivent jusqu'à leur purge, ce n'est pas un effacement. + Widget _collectionCard(AppLocalizations l) { + return _card( + title: 'Questions de vos visiteurs', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SwitchListTile( + contentPadding: EdgeInsets.zero, + value: _collectionEnabled, + onChanged: (v) => setState(() => _collectionEnabled = v), + title: const Text('Enregistrer les questions posées au guide', + style: TextStyle(fontSize: 14)), + subtitle: Text( + _collectionEnabled + ? 'Alimente l\'onglet « Ce que demandent vos visiteurs » et le rapport des ' + 'questions restées sans réponse. Conservation 90 jours, puis seuls les ' + 'regroupements par thème sont gardés.' + : 'Aucune nouvelle question ne sera enregistrée. L\'onglet « Ce que demandent ' + 'vos visiteurs » cessera de se remplir. Les questions déjà enregistrées ' + 'restent jusqu\'à leur suppression automatique à 90 jours.', + style: TextStyle(fontSize: 12, color: kBodyTextColor), + ), + activeThumbColor: kPrimaryColor, + ), + ], + ), + ); + } + Widget _card({required String title, String? subtitle, required Widget child}) { return Card( elevation: 0, diff --git a/manager_api_new/lib/model/instance_dto.dart b/manager_api_new/lib/model/instance_dto.dart index 6a40a1e..f89cb9c 100644 --- a/manager_api_new/lib/model/instance_dto.dart +++ b/manager_api_new/lib/model/instance_dto.dart @@ -26,6 +26,7 @@ class InstanceDTO { this.guideName, this.guidePersonaPrompt, this.guideVoiceId, + this.isVisitorQuestionCollectionEnabled, this.guideFallbackMessages = const [], this.subscriptionPlanId, this.subscriptionPlan, @@ -109,6 +110,10 @@ class InstanceDTO { /// Voix Gemini TTS : "Sulafat" (Viva) ou "Umbriel" (Marco). String? guideVoiceId; + /// Le client autorise-t-il l'enregistrement des questions de ses visiteurs ? (RGPD) + /// Couper la collecte n'efface rien : les questions déjà là vivent jusqu'à leur purge. + bool? isVisitorQuestionCollectionEnabled; + /// Formulations de repli. Liste à plat : plusieurs entrées peuvent partager la même langue. List guideFallbackMessages; @@ -264,6 +269,11 @@ class InstanceDTO { } else { json[r'guideVoiceId'] = null; } + if (this.isVisitorQuestionCollectionEnabled != null) { + json[r'isVisitorQuestionCollectionEnabled'] = this.isVisitorQuestionCollectionEnabled; + } else { + json[r'isVisitorQuestionCollectionEnabled'] = null; + } json[r'guideFallbackMessages'] = this.guideFallbackMessages; if (this.subscriptionPlanId != null) { json[r'subscriptionPlanId'] = this.subscriptionPlanId; @@ -372,6 +382,8 @@ class InstanceDTO { guideName: mapValueOfType(json, r'guideName'), guidePersonaPrompt: mapValueOfType(json, r'guidePersonaPrompt'), guideVoiceId: mapValueOfType(json, r'guideVoiceId'), + isVisitorQuestionCollectionEnabled: + mapValueOfType(json, r'isVisitorQuestionCollectionEnabled'), guideFallbackMessages: TranslationDTO.listFromJson(json[r'guideFallbackMessages']), subscriptionPlanId: mapValueOfType(json, r'subscriptionPlanId'), subscriptionPlan: SubscriptionPlanDTO.fromJson(json[r'subscriptionPlan']),