basic container sugegstion implementation

This commit is contained in:
Fabian Freund
2025-10-01 08:00:06 +02:00
parent 6962270db7
commit 5651b9255d
4 changed files with 97 additions and 27 deletions
@@ -279,6 +279,7 @@ class _TabSheetHeader extends HookConsumerWidget {
);
return ContainerChips(
showGroupSuggestions: true,
selectedContainer: selectedContainer,
onSelected: (container) async {
if (container != null) {
@@ -134,8 +134,8 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
return neighbors;
}
Future<List<List<String>>?> suggestClusters({
required List<String> unassignedDocumentsInput,
Future<List<({String? topic, List<String> tabIds})>?> suggestClusters({
required Map<String, String> unassignedDocumentsInput,
}) async {
if (!ref.read(
generalSettingsWithDefaultsProvider.select(
@@ -146,7 +146,9 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
}
final processedDocuments = <String, String>{};
final unassignedDocumentsProcessed = unassignedDocumentsInput.map((doc) {
final unassignedDocumentsProcessed = unassignedDocumentsInput.values.map((
doc,
) {
final processed = preprocessText(doc);
if (processed != doc) {
processedDocuments[processed] = doc;
@@ -168,9 +170,25 @@ class GeckoInferenceRepository extends _$GeckoInferenceRepository {
.toList(),
);
print(clusters);
final clusterResult = await clusters.mapNotNull(
(cluster) => Future.wait(
cluster.map((clusterTitles) async {
final topic = await predictDocumentTopic(clusterTitles.toSet());
return null;
return (
topic: topic,
tabIds: clusterTitles.map((title) {
final originalTitle = processedDocuments[title] ?? title;
return unassignedDocumentsInput.entries
.firstWhere((entry) => entry.value == originalTitle)
.key;
}).toList(),
);
}),
),
);
return clusterResult;
}
Future<Map<String, List<double>>?> generateDocumentEmbeddings(
@@ -236,26 +254,25 @@ Future<String?> containerTopic(Ref ref, String containerId) async {
}
@Riverpod()
Future<List<List<String>>?> suggestClusters(Ref ref) async {
Future<List<({List<String> tabIds, String? topic})>?> suggestClusters(
Ref ref,
) async {
final unassignedTitles = await ref.watch(
containerTabsDataProvider(null).selectAsync(
(tabData) => EquatableValue(
tabData
.where((tab) => tab.title.isNotEmpty)
.map((tab) => (tab.id, tab.title!))
.toSet(),
Map.fromEntries(
tabData
.where((tab) => tab.title.isNotEmpty)
.map((tab) => MapEntry(tab.id, tab.title!)),
),
),
),
);
if (unassignedTitles.value.isNotEmpty) {
await ref
return await ref
.read(geckoInferenceRepositoryProvider.notifier)
.suggestClusters(
unassignedDocumentsInput: unassignedTitles.value
.map((tab) => tab.$2)
.toList(),
);
.suggestClusters(unassignedDocumentsInput: unassignedTitles.value);
}
return null;
@@ -42,7 +42,7 @@ final class GeckoInferenceRepositoryProvider
}
String _$geckoInferenceRepositoryHash() =>
r'1f08047bee912a475bfd0851f8a9868a41bca836';
r'eb174c9f80f3537bc075ebbffcef787ec55ad217';
abstract class _$GeckoInferenceRepository extends $Notifier<void> {
void build();
@@ -138,13 +138,13 @@ const suggestClustersProvider = SuggestClustersProvider._();
final class SuggestClustersProvider
extends
$FunctionalProvider<
AsyncValue<List<List<String>>?>,
List<List<String>>?,
FutureOr<List<List<String>>?>
AsyncValue<List<({List<String> tabIds, String? topic})>?>,
List<({List<String> tabIds, String? topic})>?,
FutureOr<List<({List<String> tabIds, String? topic})>?>
>
with
$FutureModifier<List<List<String>>?>,
$FutureProvider<List<List<String>>?> {
$FutureModifier<List<({List<String> tabIds, String? topic})>?>,
$FutureProvider<List<({List<String> tabIds, String? topic})>?> {
const SuggestClustersProvider._()
: super(
from: null,
@@ -161,17 +161,16 @@ final class SuggestClustersProvider
@$internal
@override
$FutureProviderElement<List<List<String>>?> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
$FutureProviderElement<List<({List<String> tabIds, String? topic})>?>
$createElement($ProviderPointer pointer) => $FutureProviderElement(pointer);
@override
FutureOr<List<List<String>>?> create(Ref ref) {
FutureOr<List<({List<String> tabIds, String? topic})>?> create(Ref ref) {
return suggestClusters(ref);
}
}
String _$suggestClustersHash() => r'9d212158b80ba53afe305db938ab399b1e931d88';
String _$suggestClustersHash() => r'592058ed092384c37a185dcd5657276a3dde5b4e';
@ProviderFor(containerTabSuggestions)
const containerTabSuggestionsProvider = ContainerTabSuggestionsFamily._();
@@ -23,16 +23,21 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/controllers/tab_suggestions.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/gecko_inference.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/container_title.dart';
import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/tab_drag_container_target.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/widgets/selectable_chips.dart';
class ContainerChips extends HookConsumerWidget {
final bool displayMenu;
final bool showGroupSuggestions;
final ContainerData? selectedContainer;
final bool Function(ContainerDataWithCount)? containerFilter;
@@ -48,6 +53,7 @@ class ContainerChips extends HookConsumerWidget {
this.containerFilter,
this.searchTextListenable,
this.displayMenu = true,
this.showGroupSuggestions = false,
});
@override
@@ -132,6 +138,53 @@ class ContainerChips extends HookConsumerWidget {
},
),
),
if (showGroupSuggestions)
Consumer(
builder: (context, ref, child) {
final tabSuggestionsEnabled = ref.watch(
tabSuggestionsControllerProvider,
);
final enableAiFeatures = ref.watch(
generalSettingsWithDefaultsProvider.select(
(settings) => settings.enableLocalAiFeatures,
),
);
if (!enableAiFeatures || !tabSuggestionsEnabled) {
return const SizedBox.shrink();
}
final suggestions = ref.watch(
suggestClustersProvider,
);
return suggestions.when(
data: (data) {
if (data.isEmpty) {
return const SizedBox.shrink();
}
return FilterChip(
avatar: const Icon(MdiIcons.autoFix),
label: Text(data!.length.toString()),
showCheckmark: false,
onSelected: (value) {},
);
},
error: (error, stackTrace) {
logger.e(
'Error suggesting containers',
error: error,
stackTrace: stackTrace,
);
return const SizedBox.shrink();
},
loading: () {
return const SizedBox.shrink();
},
);
},
),
],
availableItems: availableContainers,
selectedItem: selectedContainer,