14 files changed
+92
-2
lib/features/chat/views/components/chat_bottom_bar.dart
+15
@@ -6,6 +6,8 @@ import 'package:localmind/features/chat/views/components/chat_input_bar.dart';
6
import 'package:localmind/features/chat/views/components/top_bar/smart_reply_chips.dart';
7
import 'package:localmind/features/chat/providers/chat_providers.dart';
8
import 'package:localmind/features/conversations/providers/conversation_providers.dart' as conv;
9
+import 'package:localmind/features/models/views/model_picker_sheet.dart';
10
+import 'package:localmind/l10n/app_localizations.dart';
11
12
class ChatBottomBar extends ConsumerWidget {
13
const ChatBottomBar({
@@ -107,6 +109,19 @@ class SmartReplyChipsWrapper extends ConsumerWidget {
109
Widget build(BuildContext context, WidgetRef ref) {
110
return SmartReplyChips(
111
onSend: (message) {
112
+ if (ref.read(selectedModelProvider) == null) {
113
+ final l10n = AppLocalizations.of(context)!;
114
+ ScaffoldMessenger.of(context).showSnackBar(
115
+ SnackBar(content: Text(l10n.model_required_toast)),
116
+ );
117
+ showModalBottomSheet(
118
+ context: context,
119
+ isScrollControlled: true,
120
+ useSafeArea: true,
121
+ builder: (_) => const ModelPickerSheet(),
122
+ );
123
+ return;
124
+ }
125
ref.read(chatProvider.notifier).sendMessage(message);
126
},
127
);
lib/features/chat/views/components/chat_input_bar.dart
+23
@@ -19,6 +19,7 @@ import '../../../stt/providers/stt_providers.dart';
19
import '../../providers/chat_providers.dart';
20
import '../../utils/attachment_helpers.dart';
21
import '../../utils/image_upload_utils.dart';
22
+import '../../../models/views/model_picker_sheet.dart';
23
import 'image_preview_dialog.dart';
24
25
class ChatInputBar extends ConsumerStatefulWidget {
@@ -429,6 +430,8 @@ class ChatInputBarState extends ConsumerState<ChatInputBar>
430
431
if (text.isEmpty && _attachedFiles.isEmpty) return;
432
433
+ if (!_ensureModelSelected()) return;
434
+
435
Haptics.vibrate(HapticsType.medium);
436
437
if (_sendAsAssistant) {
@@ -455,6 +458,8 @@ class ChatInputBarState extends ConsumerState<ChatInputBar>
458
final text = _controller.text.trim();
459
if (text.isEmpty && _attachedFiles.isEmpty) return;
460
461
+ if (!_ensureModelSelected()) return;
462
+
463
Haptics.vibrate(HapticsType.medium);
464
ref
465
.read(chatProvider.notifier)
@@ -471,6 +476,24 @@ class ChatInputBarState extends ConsumerState<ChatInputBar>
476
});
477
}
478
479
+ /// Returns true if a model is currently selected. When no model is selected,
480
+ /// shows a SnackBar prompting the user to pick one and opens the model
481
+ /// picker sheet, then returns false so the caller can bail out.
482
+ bool _ensureModelSelected() {
483
+ if (ref.read(selectedModelProvider) != null) return true;
484
+ final l10n = AppLocalizations.of(context)!;
485
+ ScaffoldMessenger.of(context).showSnackBar(
486
+ SnackBar(content: Text(l10n.model_required_toast)),
487
+ );
488
+ showModalBottomSheet(
489
+ context: context,
490
+ isScrollControlled: true,
491
+ useSafeArea: true,
492
+ builder: (_) => const ModelPickerSheet(),
493
+ );
494
+ return false;
495
+ }
496
+
497
void _handleStop() {
498
Haptics.vibrate(HapticsType.light);
499
lib/features/chat/views/components/message_list/message_area.dart
+17
-2
@@ -8,6 +8,7 @@ import 'package:localmind/features/chat/data/models/message.dart';
8
import 'package:localmind/features/chat/providers/chat_providers.dart';
9
import 'package:localmind/features/conversations/data/models/conversation.dart';
10
import 'package:localmind/features/conversations/providers/conversation_providers.dart' as conv;
11
+import 'package:localmind/features/models/views/model_picker_sheet.dart';
12
import 'package:localmind/features/personas/providers/personas_providers.dart';
13
import 'package:localmind/features/chat/views/components/model_info_sheet.dart';
14
import 'package:localmind/features/chat/views/components/edit_message_dialog.dart';
@@ -68,8 +69,22 @@ class MessageArea extends ConsumerWidget {
69
70
return EmptyState(
71
isCloudProvider: isCloudProvider,
71
- onQuickPrompt: (prompt) =>
72
- ref.read(chatProvider.notifier).sendMessage(prompt),
72
+ onQuickPrompt: (prompt) {
73
+ if (ref.read(selectedModelProvider) == null) {
74
+ final toastL10n = AppLocalizations.of(context)!;
75
+ ScaffoldMessenger.of(context).showSnackBar(
76
+ SnackBar(content: Text(toastL10n.model_required_toast)),
77
+ );
78
+ showModalBottomSheet(
79
+ context: context,
80
+ isScrollControlled: true,
81
+ useSafeArea: true,
82
+ builder: (_) => const ModelPickerSheet(),
83
+ );
84
+ return;
85
+ }
86
+ ref.read(chatProvider.notifier).sendMessage(prompt);
87
+ },
88
quickPrompts: [
89
l10n.quick_write,
90
l10n.quick_explain,
lib/l10n/app_en.arb
+4
@@ -3093,5 +3093,9 @@
3093
"folder_name_required": "Please enter a folder name",
3094
"@folder_name_required": {
3095
"description": "Validation error when a folder name is empty"
3096
+ },
3097
+ "model_required_toast": "You need to select a model first",
3098
+ "@model_required_toast": {
3099
+ "description": "Toast shown when the user tries to send a message but no model is selected"
3100
}
3101
}
lib/l10n/app_localizations.dart
+6
@@ -5260,6 +5260,12 @@ abstract class AppLocalizations {
5260
/// In en, this message translates to:
5261
/// **'Please enter a folder name'**
5262
String get folder_name_required;
5263
+
5264
+ /// Toast shown when the user tries to send a message but no model is selected
5265
+ ///
5266
+ /// In en, this message translates to:
5267
+ /// **'You need to select a model first'**
5268
+ String get model_required_toast;
5269
}
5270
5271
class _AppLocalizationsDelegate
lib/l10n/app_localizations_ar.dart
+3
@@ -2796,4 +2796,7 @@ class AppLocalizationsAr extends AppLocalizations {
2796
2797
@override
2798
String get folder_name_required => 'الرجاء إدخال اسم المجلد';
2799
+
2800
+ @override
2801
+ String get model_required_toast => 'You need to select a model first';
2802
}
lib/l10n/app_localizations_bn.dart
+3
@@ -2816,4 +2816,7 @@ class AppLocalizationsBn extends AppLocalizations {
2816
2817
@override
2818
String get folder_name_required => 'অনুগ্রহ করে একটি ফোল্ডারের নাম লিখুন';
2819
+
2820
+ @override
2821
+ String get model_required_toast => 'You need to select a model first';
2822
}
lib/l10n/app_localizations_en.dart
+3
@@ -2804,4 +2804,7 @@ class AppLocalizationsEn extends AppLocalizations {
2804
2805
@override
2806
String get folder_name_required => 'Please enter a folder name';
2807
+
2808
+ @override
2809
+ String get model_required_toast => 'You need to select a model first';
2810
}
lib/l10n/app_localizations_es.dart
+3
@@ -2820,4 +2820,7 @@ class AppLocalizationsEs extends AppLocalizations {
2820
2821
@override
2822
String get folder_name_required => 'Introduce un nombre de carpeta';
2823
+
2824
+ @override
2825
+ String get model_required_toast => 'You need to select a model first';
2826
}
lib/l10n/app_localizations_hi.dart
+3
@@ -2809,4 +2809,7 @@ class AppLocalizationsHi extends AppLocalizations {
2809
2810
@override
2811
String get folder_name_required => 'कृपया फ़ोल्डर का नाम दर्ज करें';
2812
+
2813
+ @override
2814
+ String get model_required_toast => 'You need to select a model first';
2815
}
lib/l10n/app_localizations_it.dart
+3
@@ -2823,4 +2823,7 @@ class AppLocalizationsIt extends AppLocalizations {
2823
2824
@override
2825
String get folder_name_required => 'Inserisci un nome per la cartella';
2826
+
2827
+ @override
2828
+ String get model_required_toast => 'You need to select a model first';
2829
}
lib/l10n/app_localizations_ja.dart
+3
@@ -2756,4 +2756,7 @@ class AppLocalizationsJa extends AppLocalizations {
2756
2757
@override
2758
String get folder_name_required => 'フォルダ名を入力してください';
2759
+
2760
+ @override
2761
+ String get model_required_toast => 'You need to select a model first';
2762
}
lib/l10n/app_localizations_ru.dart
+3
@@ -2823,4 +2823,7 @@ class AppLocalizationsRu extends AppLocalizations {
2823
2824
@override
2825
String get folder_name_required => 'Введите название папки';
2826
+
2827
+ @override
2828
+ String get model_required_toast => 'You need to select a model first';
2829
}
lib/l10n/app_localizations_zh.dart
+3
@@ -2736,6 +2736,9 @@ class AppLocalizationsZh extends AppLocalizations {
2736
2737
@override
2738
String get folder_name_required => '请输入文件夹名称';
2739
+
2740
+ @override
2741
+ String get model_required_toast => 'You need to select a model first';
2742
}
2743
2744
/// The translations for Chinese, as used in Taiwan (`zh_TW`).