local ai killswitch

This commit is contained in:
Fabian Freund
2025-07-25 22:18:08 +02:00
parent 9ae21190f4
commit dd40676b36
8 changed files with 100 additions and 27 deletions
@@ -25,6 +25,7 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/ta
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab_search.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_chips.dart';
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/hooks/listenable_callback.dart';
import 'package:weblibre/presentation/widgets/speech_to_text_button.dart';
@@ -108,6 +109,12 @@ class _TabSheetHeader extends HookConsumerWidget {
() => searchTextController.text.isNotEmpty,
);
final enableAiFeatures = ref.watch(
generalSettingsRepositoryProvider.select(
(settings) => settings.enableLocalAiFeatures,
),
);
useListenableCallback(searchTextController, () async {
await ref
.read(
@@ -160,27 +167,29 @@ class _TabSheetHeader extends HookConsumerWidget {
.toggle();
},
),
Consumer(
builder: (context, ref, child) {
final tabSuggestionsEnabled = ref.watch(
tabSuggestionsControllerProvider,
);
if (enableAiFeatures)
Consumer(
builder: (context, ref, child) {
final tabSuggestionsEnabled = ref.watch(
tabSuggestionsControllerProvider,
);
return IconButton.filledTonal(
icon: const Icon(MdiIcons.imageAutoAdjust),
isSelected: tabSuggestionsEnabled,
iconSize: 18,
padding: EdgeInsets.zero,
onPressed: () {
ref
.read(
tabSuggestionsControllerProvider.notifier,
)
.toggle();
},
);
},
),
return IconButton.filledTonal(
icon: const Icon(MdiIcons.imageAutoAdjust),
isSelected: tabSuggestionsEnabled,
iconSize: 18,
padding: EdgeInsets.zero,
onPressed: () {
ref
.read(
tabSuggestionsControllerProvider
.notifier,
)
.toggle();
},
);
},
),
],
),
TextButton.icon(
@@ -215,9 +224,9 @@ class _TabSheetHeader extends HookConsumerWidget {
);
if (result == true) {
await ref
.read(tabDataRepositoryProvider.notifier)
.closeAllTabsByContainer(container);
await ref
.read(tabDataRepositoryProvider.notifier)
.closeAllTabsByContainer(container);
}
},
icon: const Icon(MdiIcons.closeBoxMultiple),
@@ -380,9 +389,16 @@ class ViewTabsSheetWidget extends HookConsumerWidget {
final tabSuggestionsEnabled = ref.watch(
tabSuggestionsControllerProvider,
);
final enableAiFeatures = ref.watch(
generalSettingsRepositoryProvider.select(
(settings) => settings.enableLocalAiFeatures,
),
);
final suggestedTabEntities = ref.watch(
suggestedTabEntitiesProvider(
tabSuggestionsEnabled ? containerId : null,
(enableAiFeatures && tabSuggestionsEnabled)
? containerId
: null,
),
);
@@ -10,6 +10,7 @@ import 'package:synchronized/synchronized.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/embedding_text_processing.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/nearest_neighbor.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/utils/lru_cache.dart';
part 'gecko_inference.g.dart';
@@ -41,6 +42,14 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
}
Future<String?> predictDocumentTopic(Set<String> titles) async {
if (!ref.read(
generalSettingsRepositoryProvider.select(
(settings) => settings.enableLocalAiFeatures,
),
)) {
return null;
}
if (titles.isNotEmpty) {
if (_topicCache.get(titles) case final String title) {
return title;
@@ -69,6 +78,14 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
required List<String> assignedDocumentsInput,
required List<String> unassignedDocumentsInput,
}) async {
if (!ref.read(
generalSettingsRepositoryProvider.select(
(settings) => settings.enableLocalAiFeatures,
),
)) {
return null;
}
final processedDocuments = <String, String>{};
final unassignedDocumentsProcessed = unassignedDocumentsInput.map((doc) {
final processed = preprocessText(doc);
@@ -276,7 +276,7 @@ class _ContainerTabSuggestionsProviderElement
}
String _$geckoInferenceRepositoryHash() =>
r'bdae6dab14c33c6503f37f2e17b8d50fb820ac26';
r'170bd0b67e5f1edc43ed97d4535f41353fb8796e';
/// See also [GeckoInferenceRepository].
@ProviderFor(GeckoInferenceRepository)
@@ -156,6 +156,22 @@ class GeneralSettingsScreen extends HookConsumerWidget {
],
),
),
SwitchListTile.adaptive(
title: const Text('On Device AI'),
subtitle: const Text(
'Local on-device features including container topic and tab suggestions',
),
secondary: const Icon(MdiIcons.creation),
value: generalSettings.enableLocalAiFeatures,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) => currentSettings.copyWith
.enableLocalAiFeatures(value),
);
},
),
SwitchListTile.adaptive(
title: const Text('Enable Reader Mode'),
subtitle: const Text(
@@ -34,6 +34,7 @@ class GeneralSettings with FastEquatable {
final SearchSuggestionProviders defaultSearchSuggestionsProvider;
final bool createChildTabsOption;
final bool showExtensionShortcut;
final bool enableLocalAiFeatures;
final bool proxyPrivateTabsTor;
@@ -47,6 +48,7 @@ class GeneralSettings with FastEquatable {
required this.createChildTabsOption,
required this.proxyPrivateTabsTor,
required this.showExtensionShortcut,
required this.enableLocalAiFeatures,
});
GeneralSettings.withDefaults({
@@ -59,6 +61,7 @@ class GeneralSettings with FastEquatable {
bool? createChildTabsOption,
bool? proxyPrivateTabsTor,
bool? showExtensionShortcut,
bool? enableLocalAiFeatures,
}) : themeMode = themeMode ?? ThemeMode.dark,
enableReadability = enableReadability ?? true,
enforceReadability = enforceReadability ?? false,
@@ -67,7 +70,8 @@ class GeneralSettings with FastEquatable {
defaultSearchSuggestionsProvider ?? _fallbackAutocompleteProvider,
createChildTabsOption = createChildTabsOption ?? false,
proxyPrivateTabsTor = proxyPrivateTabsTor ?? false,
showExtensionShortcut = showExtensionShortcut ?? false;
showExtensionShortcut = showExtensionShortcut ?? false,
enableLocalAiFeatures = enableLocalAiFeatures ?? true;
factory GeneralSettings.fromJson(Map<String, dynamic> json) =>
_$GeneralSettingsFromJson(json);
@@ -85,5 +89,6 @@ class GeneralSettings with FastEquatable {
createChildTabsOption,
showExtensionShortcut,
proxyPrivateTabsTor,
enableLocalAiFeatures,
];
}
@@ -29,6 +29,8 @@ abstract class _$GeneralSettingsCWProxy {
GeneralSettings showExtensionShortcut(bool showExtensionShortcut);
GeneralSettings enableLocalAiFeatures(bool enableLocalAiFeatures);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
@@ -45,6 +47,7 @@ abstract class _$GeneralSettingsCWProxy {
bool createChildTabsOption,
bool proxyPrivateTabsTor,
bool showExtensionShortcut,
bool enableLocalAiFeatures,
});
}
@@ -91,6 +94,10 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
GeneralSettings showExtensionShortcut(bool showExtensionShortcut) =>
this(showExtensionShortcut: showExtensionShortcut);
@override
GeneralSettings enableLocalAiFeatures(bool enableLocalAiFeatures) =>
this(enableLocalAiFeatures: enableLocalAiFeatures);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `GeneralSettings(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
@@ -108,6 +115,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? createChildTabsOption = const $CopyWithPlaceholder(),
Object? proxyPrivateTabsTor = const $CopyWithPlaceholder(),
Object? showExtensionShortcut = const $CopyWithPlaceholder(),
Object? enableLocalAiFeatures = const $CopyWithPlaceholder(),
}) {
return GeneralSettings(
themeMode: themeMode == const $CopyWithPlaceholder()
@@ -151,6 +159,11 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.showExtensionShortcut
// ignore: cast_nullable_to_non_nullable
: showExtensionShortcut as bool,
enableLocalAiFeatures:
enableLocalAiFeatures == const $CopyWithPlaceholder()
? _value.enableLocalAiFeatures
// ignore: cast_nullable_to_non_nullable
: enableLocalAiFeatures as bool,
);
}
}
@@ -182,6 +195,7 @@ GeneralSettings _$GeneralSettingsFromJson(Map<String, dynamic> json) =>
createChildTabsOption: json['createChildTabsOption'] as bool?,
proxyPrivateTabsTor: json['proxyPrivateTabsTor'] as bool?,
showExtensionShortcut: json['showExtensionShortcut'] as bool?,
enableLocalAiFeatures: json['enableLocalAiFeatures'] as bool?,
);
Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
@@ -198,6 +212,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(GeneralSettings instance) =>
.defaultSearchSuggestionsProvider]!,
'createChildTabsOption': instance.createChildTabsOption,
'showExtensionShortcut': instance.showExtensionShortcut,
'enableLocalAiFeatures': instance.enableLocalAiFeatures,
'proxyPrivateTabsTor': instance.proxyPrivateTabsTor,
};
@@ -60,6 +60,10 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
DriftSqlType.bool,
db.typeMapping,
),
'enableLocalAiFeatures': settings['enableLocalAiFeatures']?.readAs(
DriftSqlType.bool,
db.typeMapping,
),
});
}
@@ -7,7 +7,7 @@ part of 'general_settings.dart';
// **************************************************************************
String _$generalSettingsRepositoryHash() =>
r'eb5f27e0bd8f5def833e1c703cb42181acc2440f';
r'b3c60b14cdfba7e0bcf4817addc8295b5281a9b5';
/// See also [GeneralSettingsRepository].
@ProviderFor(GeneralSettingsRepository)