From ac5d9d99f1c79bd3291db9dbfa8131635200fbae Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 28 Aug 2024 13:46:58 +0200 Subject: [PATCH] [mob][photos] ML user developer options --- mobile/lib/db/ml/db.dart | 16 +++ .../machine_learning_settings_page.dart | 29 ++++- .../ui/settings/ml/ml_user_dev_screen.dart | 103 ++++++++++++++++++ 3 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 mobile/lib/ui/settings/ml/ml_user_dev_screen.dart diff --git a/mobile/lib/db/ml/db.dart b/mobile/lib/db/ml/db.dart index 5f5cb7af35..3103e80442 100644 --- a/mobile/lib/db/ml/db.dart +++ b/mobile/lib/db/ml/db.dart @@ -668,6 +668,22 @@ class MLDataDB { return maps.first['count'] as int; } + Future> getErroredFileIDs() async { + final db = await instance.asyncDB; + final List> maps = await db.getAll( + 'SELECT DISTINCT $fileIDColumn FROM $facesTable WHERE $faceScore < 0', + ); + return maps.map((e) => e[fileIDColumn] as int).toSet(); + } + + Future deleteFaceIndexForFiles(List fileIDs) async { + final db = await instance.asyncDB; + final String sql = ''' + DELETE FROM $facesTable WHERE $fileIDColumn IN (${fileIDs.join(", ")}) + '''; + await db.execute(sql); + } + Future getClusteredOrFacelessFileCount() async { final db = await instance.asyncDB; final List> clustered = await db.getAll( diff --git a/mobile/lib/ui/settings/machine_learning_settings_page.dart b/mobile/lib/ui/settings/machine_learning_settings_page.dart index cf83d4a800..8267648ad4 100644 --- a/mobile/lib/ui/settings/machine_learning_settings_page.dart +++ b/mobile/lib/ui/settings/machine_learning_settings_page.dart @@ -26,6 +26,7 @@ import "package:photos/ui/components/title_bar_title_widget.dart"; import "package:photos/ui/components/title_bar_widget.dart"; import "package:photos/ui/components/toggle_switch_widget.dart"; import "package:photos/ui/settings/ml/enable_ml_consent.dart"; +import "package:photos/ui/settings/ml/ml_user_dev_screen.dart"; import "package:photos/utils/ml_util.dart"; import "package:photos/utils/network_util.dart"; import "package:photos/utils/wakelock_util.dart"; @@ -42,6 +43,8 @@ class _MachineLearningSettingsPageState extends State { final EnteWakeLock _wakeLock = EnteWakeLock(); Timer? _timer; + int _titleTapCount = 0; + Timer? _advancedOptionsTimer; @override void initState() { @@ -55,6 +58,9 @@ class _MachineLearningSettingsPageState } }); } + _advancedOptionsTimer = Timer.periodic(const Duration(seconds: 7), (timer) { + _titleTapCount = 0; + }); } @override @@ -63,6 +69,7 @@ class _MachineLearningSettingsPageState _wakeLock.disable(); MachineLearningController.instance.forceOverrideML(turnOn: false); _timer?.cancel(); + _advancedOptionsTimer?.cancel(); } @override @@ -73,8 +80,26 @@ class _MachineLearningSettingsPageState primary: false, slivers: [ TitleBarWidget( - flexibleSpaceTitle: TitleBarTitleWidget( - title: S.of(context).machineLearning, + flexibleSpaceTitle: GestureDetector( + child: TitleBarTitleWidget( + title: S.of(context).machineLearning, + ), + onTap: () { + setState(() { + _titleTapCount++; + if (_titleTapCount >= 7) { + _titleTapCount = 0; + // showShortToast(context, "Advanced options enabled"); + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return const MLUserDeveloperOptions(); + }, + ), + ).ignore(); + } + }); + }, ), actionIcons: [ IconButtonWidget( diff --git a/mobile/lib/ui/settings/ml/ml_user_dev_screen.dart b/mobile/lib/ui/settings/ml/ml_user_dev_screen.dart new file mode 100644 index 0000000000..8d70cee21b --- /dev/null +++ b/mobile/lib/ui/settings/ml/ml_user_dev_screen.dart @@ -0,0 +1,103 @@ +import "package:flutter/material.dart"; +import "package:photos/core/event_bus.dart"; +import "package:photos/db/ml/clip_db.dart"; +import "package:photos/db/ml/db.dart"; +import "package:photos/events/people_changed_event.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/buttons/button_widget.dart"; +import "package:photos/ui/components/models/button_type.dart"; +import "package:photos/ui/components/title_bar_title_widget.dart"; +import "package:photos/ui/components/title_bar_widget.dart"; +import "package:photos/utils/dialog_util.dart"; +import "package:photos/utils/toast_util.dart"; + +class MLUserDeveloperOptions extends StatelessWidget { + const MLUserDeveloperOptions({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + primary: false, + slivers: [ + const TitleBarWidget( + flexibleSpaceTitle: TitleBarTitleWidget( + title: "ML debug options", + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (delegateBuildContext, index) => Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column( + children: [ + Text( + "Only use if you know what you're doing", + textAlign: TextAlign.left, + style: getEnteTextTheme(context).body.copyWith( + color: getEnteColorScheme(context).textMuted, + ), + ), + const SizedBox(height: 48), + ButtonWidget( + buttonType: ButtonType.neutral, + labelText: "Purge empty indices", + onTap: () async { + await deleteEmptyIndices(context); + }, + ), + const SizedBox(height: 24), + ButtonWidget( + buttonType: ButtonType.neutral, + labelText: "Reset all local ML", + onTap: () async { + await deleteAllLocalML(context); + }, + ), + const SafeArea( + child: SizedBox( + height: 12, + ), + ), + ], + ), + ), + childCount: 1, + ), + ), + ], + ), + ); + } + + Future deleteEmptyIndices(BuildContext context) async { + try { + final Set emptyFileIDs = await MLDataDB.instance.getErroredFileIDs(); + await MLDataDB.instance.deleteFaceIndexForFiles(emptyFileIDs.toList()); + await MLDataDB.instance.deleteEmbeddings(emptyFileIDs.toList()); + showShortToast(context, "Deleted ${emptyFileIDs.length} entries"); + } catch (e) { + // ignore: unawaited_futures + showGenericErrorDialog( + context: context, + error: e, + ); + } + } + + Future deleteAllLocalML(BuildContext context) async { + try { + await MLDataDB.instance.dropClustersAndPersonTable(faces: true); + await SemanticSearchService.instance.clearIndexes(); + Bus.instance.fire(PeopleChangedEvent()); + showShortToast(context, "All local ML cleared"); + } catch (e) { + // ignore: unawaited_futures + showGenericErrorDialog( + context: context, + error: e, + ); + } + } +}