From 9af4f8e2d8a07ed4297f60b6c82ad2cc23b5932d Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Thu, 13 Aug 2026 15:58:36 +0200 Subject: [PATCH] =?UTF-8?q?Lot=20J=20:=20interrupteur=20de=20collecte=20de?= =?UTF-8?q?s=20questions=20dans=20l'=C3=A9cran=20Guide=20IA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sans UI, le client ne pouvait pas exercer le refus dont il est pourtant responsable. Le libellé dit ce que couper coûte (l'onglet cesse de se remplir) et ce que couper ne fait pas : rien n'est effacé, les questions déjà enregistrées vivent jusqu'à leur purge à 90 jours. manager_api_new étendu à la main. flutter build web vert. Co-Authored-By: Claude Opus 5 --- lib/Screens/GuideIa/guide_ia_screen.dart | 44 +++++++++++++++++++++ manager_api_new/lib/model/instance_dto.dart | 12 ++++++ 2 files changed, 56 insertions(+) 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']),