suggest title based on selection

This commit is contained in:
Fabian Freund
2025-10-02 15:47:25 +02:00
parent cb7047d9a9
commit e46f84d055
3 changed files with 236 additions and 5 deletions
@@ -27,6 +27,7 @@ import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:synchronized/synchronized.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/utils/lru_cache.dart';
@@ -251,6 +252,33 @@ Future<String?> containerTopic(Ref ref, String containerId) async {
if (!ref.mounted) return null;
final topic = await ref.watch(topicSuggestionProvider(titles).future);
if (ref.mounted && topic.isNotEmpty) {
ref.keepAlive();
}
return topic;
}
@Riverpod()
AsyncValue<String?> tabsTopic(Ref ref, EquatableValue<Set<String>> tabIds) {
final tabTitles = ref.watch(
tabStatesProvider.select((states) {
return EquatableValue(
tabIds.value.map((tabId) => states[tabId]?.title).nonNulls.toSet(),
);
}),
);
return ref.watch(topicSuggestionProvider(tabTitles));
}
@Riverpod()
Future<String?> topicSuggestion(
Ref ref,
EquatableValue<Set<String>> titles,
) async {
final topic = await ref
.read(geckoInferenceRepositoryProvider.notifier)
.predictDocumentTopic(titles.value);
@@ -112,7 +112,7 @@ final class ContainerTopicProvider
}
}
String _$containerTopicHash() => r'ddb306605a0e38b1d750bf4412cbd595f8f264cc';
String _$containerTopicHash() => r'c6e4cc316d3f05d0316909ec0bb58a95ad2fd57c';
final class ContainerTopicFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<String?>, String> {
@@ -132,6 +132,166 @@ final class ContainerTopicFamily extends $Family
String toString() => r'containerTopicProvider';
}
@ProviderFor(tabsTopic)
const tabsTopicProvider = TabsTopicFamily._();
final class TabsTopicProvider
extends
$FunctionalProvider<
AsyncValue<String?>,
AsyncValue<String?>,
AsyncValue<String?>
>
with $Provider<AsyncValue<String?>> {
const TabsTopicProvider._({
required TabsTopicFamily super.from,
required EquatableValue<Set<String>> super.argument,
}) : super(
retry: null,
name: r'tabsTopicProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$tabsTopicHash();
@override
String toString() {
return r'tabsTopicProvider'
''
'($argument)';
}
@$internal
@override
$ProviderElement<AsyncValue<String?>> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
AsyncValue<String?> create(Ref ref) {
final argument = this.argument as EquatableValue<Set<String>>;
return tabsTopic(ref, argument);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AsyncValue<String?> value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AsyncValue<String?>>(value),
);
}
@override
bool operator ==(Object other) {
return other is TabsTopicProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$tabsTopicHash() => r'b144f86782510333a48e92c4a2c70ac285da6bcb';
final class TabsTopicFamily extends $Family
with
$FunctionalFamilyOverride<
AsyncValue<String?>,
EquatableValue<Set<String>>
> {
const TabsTopicFamily._()
: super(
retry: null,
name: r'tabsTopicProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
TabsTopicProvider call(EquatableValue<Set<String>> tabIds) =>
TabsTopicProvider._(argument: tabIds, from: this);
@override
String toString() => r'tabsTopicProvider';
}
@ProviderFor(topicSuggestion)
const topicSuggestionProvider = TopicSuggestionFamily._();
final class TopicSuggestionProvider
extends $FunctionalProvider<AsyncValue<String?>, String?, FutureOr<String?>>
with $FutureModifier<String?>, $FutureProvider<String?> {
const TopicSuggestionProvider._({
required TopicSuggestionFamily super.from,
required EquatableValue<Set<String>> super.argument,
}) : super(
retry: null,
name: r'topicSuggestionProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$topicSuggestionHash();
@override
String toString() {
return r'topicSuggestionProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<String?> $createElement($ProviderPointer pointer) =>
$FutureProviderElement(pointer);
@override
FutureOr<String?> create(Ref ref) {
final argument = this.argument as EquatableValue<Set<String>>;
return topicSuggestion(ref, argument);
}
@override
bool operator ==(Object other) {
return other is TopicSuggestionProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$topicSuggestionHash() => r'5401b1cd91e53be6ad1b4c7a39a5253d380e05d4';
final class TopicSuggestionFamily extends $Family
with
$FunctionalFamilyOverride<
FutureOr<String?>,
EquatableValue<Set<String>>
> {
const TopicSuggestionFamily._()
: super(
retry: null,
name: r'topicSuggestionProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
TopicSuggestionProvider call(EquatableValue<Set<String>> titles) =>
TopicSuggestionProvider._(argument: titles, from: this);
@override
String toString() => r'topicSuggestionProvider';
}
@ProviderFor(suggestClusters)
const suggestClustersProvider = SuggestClustersProvider._();
@@ -1,9 +1,13 @@
import 'dart:math' as math;
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/material.dart';
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:skeletonizer/skeletonizer.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/core/uuid.dart';
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/sheets/view_tabs.dart';
@@ -64,8 +68,35 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
itemId: (container) => container,
itemAvatar: (container) =>
const Icon(MdiIcons.creation, size: 20),
itemLabel: (container) =>
Text(container.topic ?? 'Untitled'),
itemLabel: (container) => HookConsumer(
builder: (context, ref, child) {
final items = useListenableSelector(
selectedContainerTabs,
() => selectedContainerTabs.value[container],
);
final topic = items.isNotEmpty
? ref.watch(
tabsTopicProvider(EquatableValue(items!)),
)
: AsyncValue.data(container.topic);
return topic.when(
data: (topic) => Text(topic ?? 'Untitled'),
error: (error, stackTrace) {
logger.e(
'Failed predicting selected tabs topic',
error: error,
stackTrace: stackTrace,
);
return Text(container.topic ?? 'Untitled');
},
loading: () =>
const Skeletonizer(child: Text('Untitled')),
);
},
),
itemBadgeCount: (container) => container.tabIds.length,
availableItems: suggestions!,
selectedItem: selectedContainer.value,
@@ -150,7 +181,19 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
final initialContainer = ContainerData(
id: uuid.v7(),
color: initialColor,
name: selectedContainer.value?.topic,
name:
(ref.exists(
tabsTopicProvider(EquatableValue(selectedTabs!)),
))
? ref
.read(
tabsTopicProvider(
EquatableValue(selectedTabs),
),
)
.value ??
selectedContainer.value?.topic
: selectedContainer.value?.topic,
);
if (context.mounted) {
@@ -159,7 +202,7 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget {
).push<ContainerData?>(context);
if (result != null) {
for (final tab in selectedTabs!) {
for (final tab in selectedTabs) {
await ref
.read(tabDataRepositoryProvider.notifier)
.assignContainer(tab, result);