feat(ai-training): ajout validation OpenCV, gestion des logs et modération des contributions
- Backend IA : - Intégration d'OpenCV (WebAssembly) pour la détection et certification des cibles (anneaux concentriques, confiance). - Ajout du système de journalisation complet des uploads (SQLite, IP, appareil, statut, métadonnées). - Implémentation du système de modération (bannissement/débannissement de wallets, rejet 403 avec motifs). - Harmonisation des codes d'erreur et messages JSON (UPLOAD_SUCCESS, WALLET_BANNED, etc.). - Dashboard Next.js : page dédiée aux logs avec filtres, export CSV et actions de modération en un clic. - Application Mobile (Flutter) : - Encapsulation des réponses d'export dans AiExportResult avec gestion fine des erreurs et statuts. - Mise à jour du disclaimer de participation à l'entraînement IA avec avertissements stricts de bannissement. - Révocation de l'envoi de photos et affichage d'un bandeau explicatif en cas de suspension. - Ajout d'un bouton manuel « Actualiser mon statut » dans les paramètres pour synchroniser l'état du compte. - Possibilité de configurer l'URL/IP du serveur IA directement depuis les paramètres.
This commit is contained in:
@@ -241,15 +241,18 @@ class AnalysisProvider extends ChangeNotifier {
|
||||
/// Exporte l'image et le json vers le backend IA.
|
||||
/// [sessionId], [distance] et [weapon] proviennent du SessionProvider de
|
||||
/// l'écran appelant, pour que le dataset contienne les vraies métadonnées.
|
||||
Future<bool> exportToAiBackend({
|
||||
Future<AiExportResult> exportToAiBackend({
|
||||
String? sessionId,
|
||||
int? distance,
|
||||
String? weapon,
|
||||
}) async {
|
||||
if (_imagePath == null || _targetType == null) {
|
||||
_errorMessage = "Impossible d'export : image ou type de cible manquant.";
|
||||
_errorMessage = "Impossible d'exporter : image ou type de cible manquant.";
|
||||
notifyListeners();
|
||||
return false;
|
||||
return AiExportResult.error(
|
||||
code: 'MISSING_DATA',
|
||||
message: "Impossible d'exporter : image ou type de cible manquant.",
|
||||
);
|
||||
}
|
||||
|
||||
final service = AiExportService();
|
||||
@@ -257,7 +260,7 @@ class AnalysisProvider extends ChangeNotifier {
|
||||
_state = AnalysisState.loading;
|
||||
notifyListeners();
|
||||
|
||||
final success = await service.exportData(
|
||||
final result = await service.exportData(
|
||||
imagePath: _imagePath!,
|
||||
sessionId: sessionId ?? 'export',
|
||||
targetType: _targetType!,
|
||||
@@ -270,11 +273,11 @@ class AnalysisProvider extends ChangeNotifier {
|
||||
);
|
||||
|
||||
_state = AnalysisState.success;
|
||||
if (!success) {
|
||||
_errorMessage = "Échec de l'export vers le serveur IA.";
|
||||
if (!result.isSuccess) {
|
||||
_errorMessage = result.message;
|
||||
}
|
||||
notifyListeners();
|
||||
return success;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Save the session
|
||||
|
||||
@@ -19,6 +19,7 @@ import '../../data/repositories/session_repository.dart';
|
||||
import '../../services/score_calculator_service.dart';
|
||||
import '../../services/grouping_analyzer_service.dart';
|
||||
import '../../services/wallet_identity_service.dart';
|
||||
import '../../services/ai_export_service.dart';
|
||||
import '../session/session_provider.dart';
|
||||
import 'analysis_provider.dart';
|
||||
import 'impact_editor_screen.dart';
|
||||
@@ -933,12 +934,12 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
||||
date: sessionProvider.sessionDate,
|
||||
);
|
||||
|
||||
bool? exportSucceeded;
|
||||
AiExportResult? exportResult;
|
||||
if (export) {
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('Exportation en cours...')),
|
||||
const SnackBar(content: Text('Exportation vers le serveur IA en cours...')),
|
||||
);
|
||||
exportSucceeded = await provider.exportToAiBackend(
|
||||
exportResult = await provider.exportToAiBackend(
|
||||
sessionId: sessionProvider.activeSessionId,
|
||||
distance: sessionProvider.distance,
|
||||
weapon: sessionProvider.currentWeapon,
|
||||
@@ -954,19 +955,68 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
||||
openMainTab(mainTabStats);
|
||||
}
|
||||
|
||||
if (exportSucceeded != null) {
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
exportSucceeded
|
||||
? 'Export réussi vers le backend IA !'
|
||||
: (provider.errorMessage ?? 'Erreur d\'export'),
|
||||
if (exportResult != null) {
|
||||
if (exportResult.isBanned) {
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.block, color: Colors.white),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Participation IA suspendue : ${exportResult.reason ?? "Non-respect des règles"}',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppTheme.errorColor,
|
||||
duration: const Duration(seconds: 6),
|
||||
),
|
||||
backgroundColor: exportSucceeded
|
||||
? AppTheme.successColor
|
||||
: AppTheme.errorColor,
|
||||
),
|
||||
);
|
||||
);
|
||||
} else if (exportResult.isSuccess) {
|
||||
final targetStatus = exportResult.targetValidation?['status'];
|
||||
final isCertified = targetStatus == 'VALID';
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
Icon(
|
||||
isCertified ? Icons.verified : Icons.cloud_done,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
isCertified
|
||||
? 'Export réussi ! Cible certifiée par l\'IA.'
|
||||
: exportResult.message,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppTheme.successColor,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.warning_amber_rounded, color: Colors.white),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text('Échec de l\'export : ${exportResult.message}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppTheme.errorColor,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(
|
||||
|
||||
Reference in New Issue
Block a user