feat: add web search availability provider and enhance feature toggles in chat and tools modal
This commit is contained in:
@@ -80,12 +80,10 @@ class ThemeModeNotifier extends StateNotifier<ThemeMode> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Locale provider
|
// Locale provider
|
||||||
final localeProvider = StateNotifierProvider<LocaleNotifier, Locale?>(
|
final localeProvider = StateNotifierProvider<LocaleNotifier, Locale?>((ref) {
|
||||||
(ref) {
|
final storage = ref.watch(optimizedStorageServiceProvider);
|
||||||
final storage = ref.watch(optimizedStorageServiceProvider);
|
return LocaleNotifier(storage);
|
||||||
return LocaleNotifier(storage);
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
class LocaleNotifier extends StateNotifier<Locale?> {
|
class LocaleNotifier extends StateNotifier<Locale?> {
|
||||||
final OptimizedStorageService _storage;
|
final OptimizedStorageService _storage;
|
||||||
@@ -957,6 +955,22 @@ final imageGenerationAvailableProvider = Provider<bool>((ref) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final webSearchAvailableProvider = Provider<bool>((ref) {
|
||||||
|
final perms = ref.watch(userPermissionsProvider);
|
||||||
|
return perms.maybeWhen(
|
||||||
|
data: (data) {
|
||||||
|
final features = data['features'];
|
||||||
|
if (features is Map<String, dynamic>) {
|
||||||
|
final value = features['web_search'];
|
||||||
|
if (value is bool) return value;
|
||||||
|
if (value is String) return value.toLowerCase() == 'true';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
orElse: () => false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Folders provider
|
// Folders provider
|
||||||
final foldersProvider = FutureProvider<List<Folder>>((ref) async {
|
final foldersProvider = FutureProvider<List<Folder>>((ref) async {
|
||||||
final api = ref.watch(apiServiceProvider);
|
final api = ref.watch(apiServiceProvider);
|
||||||
|
|||||||
@@ -657,8 +657,10 @@ Future<void> _sendMessageInternal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check feature toggles for API
|
// Check feature toggles for API (gated by server availability)
|
||||||
final webSearchEnabled = ref.read(webSearchEnabledProvider);
|
final webSearchEnabled =
|
||||||
|
ref.read(webSearchEnabledProvider) &&
|
||||||
|
ref.read(webSearchAvailableProvider);
|
||||||
final imageGenerationEnabled = ref.read(imageGenerationEnabledProvider);
|
final imageGenerationEnabled = ref.read(imageGenerationEnabledProvider);
|
||||||
|
|
||||||
// Prepare tools list - pass tool IDs directly
|
// Prepare tools list - pass tool IDs directly
|
||||||
|
|||||||
@@ -65,7 +65,12 @@ class _EnhancedImageAttachmentState
|
|||||||
parent: _animationController,
|
parent: _animationController,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
_loadImage();
|
// Defer loading until after first frame to avoid accessing inherited widgets
|
||||||
|
// (e.g., Localizations) during initState
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
_loadImage();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -382,14 +382,13 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
|||||||
)!.addAttachment,
|
)!.addAttachment,
|
||||||
),
|
),
|
||||||
const SizedBox(width: Spacing.sm),
|
const SizedBox(width: Spacing.sm),
|
||||||
// Quick pills: wrap in horizontal scroller to prevent overflow
|
// Quick pills: no scroll, clip text within fixed max width
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: Row(
|
||||||
scrollDirection: Axis.horizontal,
|
children: [
|
||||||
physics: const BouncingScrollPhysics(),
|
Flexible(
|
||||||
child: Row(
|
fit: FlexFit.loose,
|
||||||
children: [
|
child: _buildPillButton(
|
||||||
_buildPillButton(
|
|
||||||
icon: Platform.isIOS
|
icon: Platform.isIOS
|
||||||
? CupertinoIcons.search
|
? CupertinoIcons.search
|
||||||
: Icons.search,
|
: Icons.search,
|
||||||
@@ -409,9 +408,12 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
if (imageGenAvailable) ...[
|
),
|
||||||
const SizedBox(width: Spacing.sm),
|
if (imageGenAvailable) ...[
|
||||||
_buildPillButton(
|
const SizedBox(width: Spacing.sm),
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.loose,
|
||||||
|
child: _buildPillButton(
|
||||||
icon: Platform.isIOS
|
icon: Platform.isIOS
|
||||||
? CupertinoIcons.photo
|
? CupertinoIcons.photo
|
||||||
: Icons.image,
|
: Icons.image,
|
||||||
@@ -431,9 +433,9 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
],
|
],
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: Spacing.sm),
|
const SizedBox(width: Spacing.sm),
|
||||||
@@ -705,20 +707,25 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: Spacing.md),
|
padding: const EdgeInsets.symmetric(horizontal: Spacing.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isActive
|
// Align with unified tools modal: keep subtle card background even when active
|
||||||
? context.conduitTheme.buttonPrimary
|
color: context.conduitTheme.cardBackground,
|
||||||
: context.conduitTheme.cardBackground,
|
|
||||||
borderRadius: BorderRadius.circular(AppBorderRadius.xl),
|
borderRadius: BorderRadius.circular(AppBorderRadius.xl),
|
||||||
// Reduce perceived height variance: only show shadow when active
|
// No elevation to match modal chips
|
||||||
boxShadow: isActive ? ConduitShadows.button : null,
|
boxShadow: null,
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: ConstrainedBox(
|
||||||
label,
|
constraints: const BoxConstraints(maxWidth: 140),
|
||||||
style: AppTypography.labelStyle.copyWith(
|
child: Text(
|
||||||
color: isActive
|
label,
|
||||||
? context.conduitTheme.buttonPrimaryText
|
maxLines: 1,
|
||||||
: context.conduitTheme.textPrimary,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: false,
|
||||||
|
style: AppTypography.labelStyle.copyWith(
|
||||||
|
color: isActive
|
||||||
|
? context.conduitTheme.buttonPrimary
|
||||||
|
: context.conduitTheme.textPrimary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class _UnifiedToolsModalState extends ConsumerState<UnifiedToolsModal> {
|
|||||||
final webSearchEnabled = ref.watch(webSearchEnabledProvider);
|
final webSearchEnabled = ref.watch(webSearchEnabledProvider);
|
||||||
final imageGenEnabled = ref.watch(imageGenerationEnabledProvider);
|
final imageGenEnabled = ref.watch(imageGenerationEnabledProvider);
|
||||||
final imageGenAvailable = ref.watch(imageGenerationAvailableProvider);
|
final imageGenAvailable = ref.watch(imageGenerationAvailableProvider);
|
||||||
|
final webSearchAvailable = ref.watch(webSearchAvailableProvider);
|
||||||
final selectedToolIds = ref.watch(selectedToolIdsProvider);
|
final selectedToolIds = ref.watch(selectedToolIdsProvider);
|
||||||
final toolsAsync = ref.watch(toolsListProvider);
|
final toolsAsync = ref.watch(toolsListProvider);
|
||||||
|
|
||||||
@@ -59,21 +60,22 @@ class _UnifiedToolsModalState extends ConsumerState<UnifiedToolsModal> {
|
|||||||
// Full tiles for Web and Image features
|
// Full tiles for Web and Image features
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
_buildFeatureTile(
|
if (webSearchAvailable)
|
||||||
title: AppLocalizations.of(context)!.webSearch,
|
_buildFeatureTile(
|
||||||
description: AppLocalizations.of(
|
title: AppLocalizations.of(context)!.webSearch,
|
||||||
context,
|
description: AppLocalizations.of(
|
||||||
)!.webSearchDescription,
|
context,
|
||||||
icon: Platform.isIOS
|
)!.webSearchDescription,
|
||||||
? CupertinoIcons.search
|
icon: Platform.isIOS
|
||||||
: Icons.search,
|
? CupertinoIcons.search
|
||||||
isActive: webSearchEnabled,
|
: Icons.search,
|
||||||
onTap: () {
|
isActive: webSearchEnabled,
|
||||||
HapticFeedback.lightImpact();
|
onTap: () {
|
||||||
ref.read(webSearchEnabledProvider.notifier).state =
|
HapticFeedback.lightImpact();
|
||||||
!webSearchEnabled;
|
ref.read(webSearchEnabledProvider.notifier).state =
|
||||||
},
|
!webSearchEnabled;
|
||||||
),
|
},
|
||||||
|
),
|
||||||
if (imageGenAvailable)
|
if (imageGenAvailable)
|
||||||
_buildFeatureTile(
|
_buildFeatureTile(
|
||||||
title: AppLocalizations.of(context)!.imageGeneration,
|
title: AppLocalizations.of(context)!.imageGeneration,
|
||||||
|
|||||||
Reference in New Issue
Block a user