Files
ente/mobile/lib/ui/settings/debug/ml_debug_section_widget.dart
2024-10-23 22:43:28 +05:30

400 lines
15 KiB
Dart

import "dart:async";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/db/ml/db.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/models/ml/face/person.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import 'package:photos/services/machine_learning/ml_service.dart';
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
import "package:photos/ui/components/toggle_switch_widget.dart";
import 'package:photos/ui/settings/common_settings.dart';
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/ml_util.dart";
import 'package:photos/utils/toast_util.dart';
class MLDebugSectionWidget extends StatefulWidget {
const MLDebugSectionWidget({super.key});
@override
State<MLDebugSectionWidget> createState() => _MLDebugSectionWidgetState();
}
class _MLDebugSectionWidgetState extends State<MLDebugSectionWidget> {
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(const Duration(seconds: 5), (timer) {
setState(() {
// Your state update logic here
});
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ExpandableMenuItemWidget(
title: "ML Debug",
selectionOptionsWidget: _getSectionOptions(context),
leadingIcon: Icons.bug_report_outlined,
);
}
Widget _getSectionOptions(BuildContext context) {
final Logger logger = Logger("MLDebugSectionWidget");
final colorScheme = getEnteColorScheme(context);
return Column(
children: [
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: FutureBuilder<IndexStatus>(
future: getIndexStatus(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final IndexStatus status = snapshot.data!;
return CaptionedTextWidget(
title: "ML (${status.indexedItems} indexed)",
);
}
return const SizedBox.shrink();
},
),
menuItemColor: colorScheme.fillFaint,
trailingWidget: ToggleSwitchWidget(
value: () => localSettings.isMLIndexingEnabled,
onChanged: () async {
try {
final isEnabled = await localSettings.toggleMLIndexing();
logger.info('ML indexing turned ${isEnabled ? 'on' : 'off'}');
if (isEnabled) {
await MLService.instance.init();
await SemanticSearchService.instance.init();
unawaited(MLService.instance.runAllML(force: true));
} else {
MLService.instance.pauseIndexingAndClustering();
}
if (mounted) {
setState(() {});
}
} catch (e, s) {
logger.warning('indexing failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
singleBorderRadius: 8,
isGestureDetectorDisabled: true,
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Remote fetch",
),
menuItemColor: colorScheme.fillFaint,
trailingWidget: ToggleSwitchWidget(
value: () => localSettings.remoteFetchEnabled,
onChanged: () async {
try {
await localSettings.toggleRemoteFetch();
logger.info(
'Remote fetch is turned ${localSettings.remoteFetchEnabled ? 'on' : 'off'}',
);
if (mounted) {
setState(() {});
}
} catch (e, s) {
logger.warning(
'Remote fetch toggle failed ',
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
singleBorderRadius: 8,
isGestureDetectorDisabled: true,
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Auto indexing",
),
menuItemColor: colorScheme.fillFaint,
trailingWidget: ToggleSwitchWidget(
value: () => !MLService.instance.debugIndexingDisabled,
onChanged: () async {
try {
MLService.instance.debugIndexingDisabled =
!MLService.instance.debugIndexingDisabled;
if (MLService.instance.debugIndexingDisabled) {
MLService.instance.pauseIndexingAndClustering();
} else {
unawaited(MLService.instance.runAllML());
}
if (mounted) {
setState(() {});
}
} catch (e, s) {
logger.warning('debugIndexingDisabled toggle failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
singleBorderRadius: 8,
isGestureDetectorDisabled: true,
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Trigger run ML",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
MLService.instance.debugIndexingDisabled = false;
unawaited(MLService.instance.runAllML());
} catch (e, s) {
logger.warning('indexAndClusterAll failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Trigger run indexing",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
MLService.instance.debugIndexingDisabled = false;
unawaited(MLService.instance.fetchAndIndexAllImages());
} catch (e, s) {
logger.warning('indexing failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: FutureBuilder<double>(
future: MLDataDB.instance.getClusteredToIndexableFilesRatio(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return CaptionedTextWidget(
title:
"Trigger clustering (${(100 * snapshot.data!).toStringAsFixed(0)}% done)",
);
}
return const SizedBox.shrink();
},
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
await PersonService.instance.fetchRemoteClusterFeedback();
MLService.instance.debugIndexingDisabled = false;
await MLService.instance.clusterAllImages();
Bus.instance.fire(PeopleChangedEvent());
showShortToast(context, "Done");
} catch (e, s) {
logger.warning('clustering failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Update discover",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
await magicCacheService.updateCache(forced: true);
} catch (e, s) {
logger.warning('Update discover failed', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Sync person mappings ",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
await faceRecognitionService.sync();
showShortToast(context, "Done");
} catch (e, s) {
logger.warning('sync person mappings failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Show empty indexes",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
final emptyFaces = await MLDataDB.instance.getErroredFaceCount();
showShortToast(context, '$emptyFaces empty faces');
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Reset faces feedback",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
alwaysShowSuccessState: true,
onTap: () async {
await showChoiceDialog(
context,
title: "Are you sure?",
body:
"This will drop all people and their related feedback stored locally. It will keep clustering labels and embeddings untouched, as well as persons stored on remote.",
firstButtonLabel: "Yes, confirm",
firstButtonOnTap: () async {
try {
await MLDataDB.instance.dropFacesFeedbackTables();
Bus.instance.fire(PeopleChangedEvent());
showShortToast(context, "Done");
} catch (e, s) {
logger.warning('reset feedback failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
);
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Reset faces feedback and clustering",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
await showChoiceDialog(
context,
title: "Are you sure?",
body:
"This will delete all people (also from remote), their related feedback and clustering labels. It will keep embeddings untouched.",
firstButtonLabel: "Yes, confirm",
firstButtonOnTap: () async {
try {
final List<PersonEntity> persons =
await PersonService.instance.getPersons();
for (final PersonEntity p in persons) {
await PersonService.instance.deletePerson(p.remoteID);
}
await MLDataDB.instance.dropClustersAndPersonTable();
Bus.instance.fire(PeopleChangedEvent());
showShortToast(context, "Done");
} catch (e, s) {
logger.warning('peopleToPersonMapping remove failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
);
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Reset all local faces",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
await showChoiceDialog(
context,
title: "Are you sure?",
body:
"This will drop all local faces data. You will need to again re-index faces.",
firstButtonLabel: "Yes, confirm",
firstButtonOnTap: () async {
try {
await MLDataDB.instance
.dropClustersAndPersonTable(faces: true);
Bus.instance.fire(PeopleChangedEvent());
showShortToast(context, "Done");
} catch (e, s) {
logger.warning('drop feedback failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
);
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Reset all local clip",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
await showChoiceDialog(
context,
title: "Are you sure?",
body:
"You will need to again re-index or fetch all clip image embeddings.",
firstButtonLabel: "Yes, confirm",
firstButtonOnTap: () async {
try {
await SemanticSearchService.instance.clearIndexes();
showShortToast(context, "Done");
} catch (e, s) {
logger.warning('drop clip embeddings failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
);
},
),
],
);
}
}