Guide IA : libellés de canaux, activation depuis la carte, notifications topRight
La carte « Où le guide est disponible » affichait la valeur entière de l'enum AppType (0, 1, 2…) au lieu d'un libellé. Réutilise les clés statsChannel* déjà traduites en FR/EN/NL pour l'écran Statistiques. L'activation de l'assistant par canal se réglait dans chaque onglet d'application, alors que le champ vit sur ApplicationInstance et se résout par (InstanceId, AppType). Le réglage passe dans la liste des canaux du Guide IA, où tous les canaux sont visibles au même endroit ; le paramètre showAssistant de AppConfigurationLinkScreen devient inutile. Les notifications de cet écran passaient par ScaffoldMessenger (en bas), contrairement au reste de l'application qui utilise showNotification (toastification, topRight). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9af4f8e2d8
commit
a6463acce6
@ -25,10 +25,9 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
class AppConfigurationLinkScreen extends StatefulWidget {
|
class AppConfigurationLinkScreen extends StatefulWidget {
|
||||||
final ApplicationInstanceDTO applicationInstanceDTO;
|
final ApplicationInstanceDTO applicationInstanceDTO;
|
||||||
final bool showAssistant;
|
|
||||||
final String? configTitle;
|
final String? configTitle;
|
||||||
final String? appUpdatedLabel;
|
final String? appUpdatedLabel;
|
||||||
AppConfigurationLinkScreen({Key? key, required this.applicationInstanceDTO, this.showAssistant = true, this.configTitle, this.appUpdatedLabel}) : super(key: key);
|
AppConfigurationLinkScreen({Key? key, required this.applicationInstanceDTO, this.configTitle, this.appUpdatedLabel}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_AppConfigurationLinkScreenState createState() => _AppConfigurationLinkScreenState();
|
_AppConfigurationLinkScreenState createState() => _AppConfigurationLinkScreenState();
|
||||||
@ -335,45 +334,8 @@ class _AppConfigurationLinkScreenState extends State<AppConfigurationLinkScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Assistant IA — visible uniquement si l'instance a la fonctionnalité
|
// L'activation de l'assistant par canal se règle dans l'onglet Guide IA,
|
||||||
if (widget.showAssistant && managerAppContext.instanceDTO?.isAssistant == true)
|
// qui liste tous les canaux au même endroit.
|
||||||
Container(
|
|
||||||
width: elementWidth,
|
|
||||||
height: elementHeight,
|
|
||||||
child: Center(
|
|
||||||
child: StatefulBuilder(
|
|
||||||
builder: (context, localSetState) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(AppLocalizations.of(context)!.aiAssistantLabel, style: TextStyle(fontSize: 16)),
|
|
||||||
Switch(
|
|
||||||
activeThumbColor: kPrimaryColor,
|
|
||||||
inactiveThumbColor: kBodyTextColor,
|
|
||||||
inactiveTrackColor: kSecond,
|
|
||||||
hoverColor: kPrimaryColor.withValues(alpha: 0.2),
|
|
||||||
value: _applicationInstanceDTO.isAssistant ?? false,
|
|
||||||
onChanged: (bool newValue) async {
|
|
||||||
try {
|
|
||||||
_applicationInstanceDTO.isAssistant = newValue;
|
|
||||||
var applicationLink = await updateApplicationInstance(appContext, _applicationInstanceDTO);
|
|
||||||
if (applicationLink != null) {
|
|
||||||
localSetState(() {
|
|
||||||
_applicationInstanceDTO.isAssistant = applicationLink.isAssistant;
|
|
||||||
});
|
|
||||||
showNotification(kSuccess, kWhite, appUpdatedMsg, context, null);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showNotification(kError, kWhite, AppLocalizations.of(context)!.errorOccurred, context, null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -153,7 +153,6 @@ class WebAppScreen extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: AppConfigurationLinkScreen(
|
child: AppConfigurationLinkScreen(
|
||||||
applicationInstanceDTO: applicationInstanceDTO,
|
applicationInstanceDTO: applicationInstanceDTO,
|
||||||
showAssistant: false,
|
|
||||||
configTitle: 'Contenu de l\'application web',
|
configTitle: 'Contenu de l\'application web',
|
||||||
appUpdatedLabel: 'Application web mise à jour',
|
appUpdatedLabel: 'Application web mise à jour',
|
||||||
),
|
),
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:manager_api_new/api.dart';
|
import 'package:manager_api_new/api.dart';
|
||||||
import 'package:manager_app/app_context.dart';
|
import 'package:manager_app/app_context.dart';
|
||||||
|
import 'package:manager_app/Components/message_notification.dart';
|
||||||
import 'package:manager_app/constants.dart';
|
import 'package:manager_app/constants.dart';
|
||||||
import 'package:manager_app/l10n/app_localizations.dart';
|
import 'package:manager_app/l10n/app_localizations.dart';
|
||||||
import 'package:manager_app/Models/managerContext.dart';
|
import 'package:manager_app/Models/managerContext.dart';
|
||||||
|
import 'package:manager_app/Screens/Applications/app_configuration_link_screen.dart';
|
||||||
import 'package:manager_app/Screens/GuideIa/visitor_questions_tab.dart';
|
import 'package:manager_app/Screens/GuideIa/visitor_questions_tab.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@ -33,6 +35,9 @@ class _GuideIaScreenState extends State<GuideIaScreen> {
|
|||||||
bool _saving = false;
|
bool _saving = false;
|
||||||
bool _reindexing = false;
|
bool _reindexing = false;
|
||||||
|
|
||||||
|
/// Ids des canaux dont l'activation est en cours — chaque ligne bascule indépendamment.
|
||||||
|
final Set<String?> _togglingChannels = {};
|
||||||
|
|
||||||
int _tab = 0;
|
int _tab = 0;
|
||||||
|
|
||||||
final _previewController = TextEditingController();
|
final _previewController = TextEditingController();
|
||||||
@ -66,8 +71,9 @@ class _GuideIaScreenState extends State<GuideIaScreen> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
ManagerAppContext get _managerContext =>
|
AppContext get _appContext => Provider.of<AppContext>(context, listen: false);
|
||||||
Provider.of<AppContext>(context, listen: false).getContext() as ManagerAppContext;
|
|
||||||
|
ManagerAppContext get _managerContext => _appContext.getContext() as ManagerAppContext;
|
||||||
|
|
||||||
/// Langue dans laquelle le gestionnaire saisit ses messages de repli.
|
/// Langue dans laquelle le gestionnaire saisit ses messages de repli.
|
||||||
/// Les autres langues sont produites par la traduction automatique, comme le reste du contenu.
|
/// Les autres langues sont produites par la traduction automatique, comme le reste du contenu.
|
||||||
@ -196,14 +202,10 @@ class _GuideIaScreenState extends State<GuideIaScreen> {
|
|||||||
try {
|
try {
|
||||||
await _managerContext.clientAPI!.instanceApi!.instanceUpdateinstance(instance);
|
await _managerContext.clientAPI!.instanceApi!.instanceUpdateinstance(instance);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showNotification(kSuccess, kWhite, l.guideIaSaved, context, null);
|
||||||
SnackBar(content: Text(l.guideIaSaved), backgroundColor: kPrimaryColor),
|
|
||||||
);
|
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showNotification(kError, kWhite, l.guideIaSaveError, context, null);
|
||||||
SnackBar(content: Text(l.guideIaSaveError), backgroundColor: Colors.redAccent),
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _saving = false);
|
if (mounted) setState(() => _saving = false);
|
||||||
}
|
}
|
||||||
@ -244,22 +246,19 @@ class _GuideIaScreenState extends State<GuideIaScreen> {
|
|||||||
|
|
||||||
if (response.statusCode == 202) {
|
if (response.statusCode == 202) {
|
||||||
final sections = (jsonDecode(response.body)['sectionsQueued'] as num?)?.toInt() ?? 0;
|
final sections = (jsonDecode(response.body)['sectionsQueued'] as num?)?.toInt() ?? 0;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
showNotification(
|
||||||
content: Text('Réindexation lancée — $sections section(s) en file. '
|
kSuccess,
|
||||||
'Le détail par section part dans les logs.'),
|
kWhite,
|
||||||
backgroundColor: kPrimaryColor,
|
'Réindexation lancée — $sections section(s) en file. '
|
||||||
));
|
'Le détail par section part dans les logs.',
|
||||||
|
context,
|
||||||
|
null);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
showNotification(kError, kWhite, 'Échec (${response.statusCode}) : ${response.body}', context, null);
|
||||||
content: Text('Échec (${response.statusCode}) : ${response.body}'),
|
|
||||||
backgroundColor: Colors.redAccent,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showNotification(kError, kWhite, 'Échec de la réindexation : $e', context, null);
|
||||||
SnackBar(content: Text('Échec de la réindexation : $e'), backgroundColor: Colors.redAccent),
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _reindexing = false);
|
if (mounted) setState(() => _reindexing = false);
|
||||||
}
|
}
|
||||||
@ -531,6 +530,44 @@ class _GuideIaScreenState extends State<GuideIaScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _channelLabel(AppLocalizations l, AppType? channel) {
|
||||||
|
switch (channel?.value) {
|
||||||
|
case 0:
|
||||||
|
return l.statsChannelMobile;
|
||||||
|
case 1:
|
||||||
|
return l.statsChannelTablet;
|
||||||
|
case 2:
|
||||||
|
return l.statsChannelWeb;
|
||||||
|
case 3:
|
||||||
|
return l.statsChannelVR;
|
||||||
|
case 4:
|
||||||
|
return l.statsChannelVoice;
|
||||||
|
default:
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _toggleChannel(ApplicationInstanceDTO app, AppLocalizations l) async {
|
||||||
|
final previous = app.isAssistant;
|
||||||
|
setState(() {
|
||||||
|
app.isAssistant = !(previous ?? false);
|
||||||
|
_togglingChannels.add(app.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final updated = await updateApplicationInstance(_appContext, app);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => app.isAssistant = updated?.isAssistant ?? app.isAssistant);
|
||||||
|
showNotification(kSuccess, kWhite, l.guideIaSaved, context, null);
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => app.isAssistant = previous);
|
||||||
|
showNotification(kError, kWhite, l.guideIaSaveError, context, null);
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _togglingChannels.remove(app.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _channelsCard(AppLocalizations l) {
|
Widget _channelsCard(AppLocalizations l) {
|
||||||
final apps = _instance?.applicationInstanceDTOs ?? [];
|
final apps = _instance?.applicationInstanceDTOs ?? [];
|
||||||
|
|
||||||
@ -540,20 +577,28 @@ class _GuideIaScreenState extends State<GuideIaScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: apps.map((app) {
|
children: apps.map((app) {
|
||||||
final on = app.isAssistant == true;
|
final on = app.isAssistant == true;
|
||||||
return Padding(
|
final busy = _togglingChannels.contains(app.id);
|
||||||
|
return InkWell(
|
||||||
|
onTap: busy ? null : () => _toggleChannel(app, l),
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 7),
|
padding: const EdgeInsets.symmetric(vertical: 7),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
if (busy)
|
||||||
|
const SizedBox(
|
||||||
|
width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2))
|
||||||
|
else
|
||||||
Icon(on ? Icons.check_circle : Icons.remove_circle_outline,
|
Icon(on ? Icons.check_circle : Icons.remove_circle_outline,
|
||||||
size: 18, color: on ? kPrimaryColor : kSecond),
|
size: 18, color: on ? kPrimaryColor : kSecond),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text((app.appType?.value ?? '—').toString(),
|
child: Text(_channelLabel(l, app.appType),
|
||||||
style: const TextStyle(fontSize: 14))),
|
style: const TextStyle(fontSize: 14))),
|
||||||
Text(on ? l.guideIaChannelOn : l.guideIaChannelOff,
|
Text(on ? l.guideIaChannelOn : l.guideIaChannelOff,
|
||||||
style: TextStyle(fontSize: 12.5, color: kBodyTextColor.withValues(alpha: 0.7))),
|
style: TextStyle(fontSize: 12.5, color: kBodyTextColor.withValues(alpha: 0.7))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
@ -690,12 +735,7 @@ class _GuideIaScreenState extends State<GuideIaScreen> {
|
|||||||
_previewTurns.last = _PreviewTurn(message, null);
|
_previewTurns.last = _PreviewTurn(message, null);
|
||||||
_previewPending = false;
|
_previewPending = false;
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showNotification(kError, kWhite, AppLocalizations.of(context)!.guideIaPreviewError, context, null);
|
||||||
SnackBar(
|
|
||||||
content: Text(AppLocalizations.of(context)!.guideIaPreviewError),
|
|
||||||
backgroundColor: Colors.redAccent,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@
|
|||||||
"guideIaUsageQuestions": "{count} questions asked",
|
"guideIaUsageQuestions": "{count} questions asked",
|
||||||
"guideIaUsageNoQuota": "No cap set on your plan.",
|
"guideIaUsageNoQuota": "No cap set on your plan.",
|
||||||
"guideIaChannelsTitle": "Where the guide is available",
|
"guideIaChannelsTitle": "Where the guide is available",
|
||||||
"guideIaChannelsSub": "Enabled per channel, from each application's settings.",
|
"guideIaChannelsSub": "Click a channel to enable or disable the guide there.",
|
||||||
"guideIaChannelOn": "Enabled",
|
"guideIaChannelOn": "Enabled",
|
||||||
"guideIaChannelOff": "Disabled",
|
"guideIaChannelOff": "Disabled",
|
||||||
"guideIaIdentityTitle": "Guide identity",
|
"guideIaIdentityTitle": "Guide identity",
|
||||||
|
|||||||
@ -62,7 +62,7 @@
|
|||||||
"guideIaUsageQuestions": "{count} questions posées",
|
"guideIaUsageQuestions": "{count} questions posées",
|
||||||
"guideIaUsageNoQuota": "Aucun plafond défini sur votre offre.",
|
"guideIaUsageNoQuota": "Aucun plafond défini sur votre offre.",
|
||||||
"guideIaChannelsTitle": "Où le guide est disponible",
|
"guideIaChannelsTitle": "Où le guide est disponible",
|
||||||
"guideIaChannelsSub": "Activable canal par canal, depuis la configuration de chaque application.",
|
"guideIaChannelsSub": "Cliquez sur un canal pour y activer ou désactiver le guide.",
|
||||||
"guideIaChannelOn": "Activé",
|
"guideIaChannelOn": "Activé",
|
||||||
"guideIaChannelOff": "Désactivé",
|
"guideIaChannelOff": "Désactivé",
|
||||||
"guideIaIdentityTitle": "Identité du guide",
|
"guideIaIdentityTitle": "Identité du guide",
|
||||||
|
|||||||
@ -445,7 +445,7 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @guideIaChannelsSub.
|
/// No description provided for @guideIaChannelsSub.
|
||||||
///
|
///
|
||||||
/// In fr, this message translates to:
|
/// In fr, this message translates to:
|
||||||
/// **'Activable canal par canal, depuis la configuration de chaque application.'**
|
/// **'Cliquez sur un canal pour y activer ou désactiver le guide.'**
|
||||||
String get guideIaChannelsSub;
|
String get guideIaChannelsSub;
|
||||||
|
|
||||||
/// No description provided for @guideIaChannelOn.
|
/// No description provided for @guideIaChannelOn.
|
||||||
|
|||||||
@ -195,7 +195,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get guideIaChannelsSub =>
|
String get guideIaChannelsSub =>
|
||||||
'Enabled per channel, from each application\'s settings.';
|
'Click a channel to enable or disable the guide there.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get guideIaChannelOn => 'Enabled';
|
String get guideIaChannelOn => 'Enabled';
|
||||||
|
|||||||
@ -198,7 +198,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get guideIaChannelsSub =>
|
String get guideIaChannelsSub =>
|
||||||
'Activable canal par canal, depuis la configuration de chaque application.';
|
'Cliquez sur un canal pour y activer ou désactiver le guide.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get guideIaChannelOn => 'Activé';
|
String get guideIaChannelOn => 'Activé';
|
||||||
|
|||||||
@ -198,7 +198,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get guideIaChannelsSub =>
|
String get guideIaChannelsSub =>
|
||||||
'Per kanaal in te schakelen, vanuit de instellingen van elke toepassing.';
|
'Klik op een kanaal om de gids daar in of uit te schakelen.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get guideIaChannelOn => 'Ingeschakeld';
|
String get guideIaChannelOn => 'Ingeschakeld';
|
||||||
|
|||||||
@ -62,7 +62,7 @@
|
|||||||
"guideIaUsageQuestions": "{count} gestelde vragen",
|
"guideIaUsageQuestions": "{count} gestelde vragen",
|
||||||
"guideIaUsageNoQuota": "Geen limiet ingesteld op uw abonnement.",
|
"guideIaUsageNoQuota": "Geen limiet ingesteld op uw abonnement.",
|
||||||
"guideIaChannelsTitle": "Waar de gids beschikbaar is",
|
"guideIaChannelsTitle": "Waar de gids beschikbaar is",
|
||||||
"guideIaChannelsSub": "Per kanaal in te schakelen, vanuit de instellingen van elke toepassing.",
|
"guideIaChannelsSub": "Klik op een kanaal om de gids daar in of uit te schakelen.",
|
||||||
"guideIaChannelOn": "Ingeschakeld",
|
"guideIaChannelOn": "Ingeschakeld",
|
||||||
"guideIaChannelOff": "Uitgeschakeld",
|
"guideIaChannelOff": "Uitgeschakeld",
|
||||||
"guideIaIdentityTitle": "Identiteit van de gids",
|
"guideIaIdentityTitle": "Identiteit van de gids",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user