neoagent 2.3.1-beta.57 → 2.3.1-beta.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_animate/flutter_animate.dart';
3
- import 'package:google_fonts/google_fonts.dart';
3
+
4
4
  import '../../main.dart';
5
+ import 'onboarding_chrome.dart';
5
6
 
6
7
  class OnboardingModelStep extends StatefulWidget {
7
8
  const OnboardingModelStep({
@@ -20,15 +21,16 @@ class OnboardingModelStep extends StatefulWidget {
20
21
  class _OnboardingModelStepState extends State<OnboardingModelStep> {
21
22
  String? _selectedModel;
22
23
 
23
- List<ModelMeta> get _models {
24
- return widget.controller.supportedModels.where((m) => m.available && !m.id.startsWith('tool:')).toList();
25
- }
24
+ List<ModelMeta> get _models => widget.controller.supportedModels
25
+ .where((m) => m.available && !m.id.startsWith('tool:'))
26
+ .toList();
26
27
 
27
28
  @override
28
29
  void initState() {
29
30
  super.initState();
30
31
  final defaultChatModel = widget.controller.defaultChatModel;
31
- if (defaultChatModel.isNotEmpty && _models.any((m) => m.id == defaultChatModel)) {
32
+ if (defaultChatModel.isNotEmpty &&
33
+ _models.any((m) => m.id == defaultChatModel)) {
32
34
  _selectedModel = defaultChatModel;
33
35
  } else if (_models.isNotEmpty) {
34
36
  _selectedModel = _models.first.id;
@@ -36,263 +38,196 @@ class _OnboardingModelStepState extends State<OnboardingModelStep> {
36
38
  }
37
39
 
38
40
  Color _getColorForModel(ModelMeta model) {
39
- final l = model.provider.toLowerCase() + model.id.toLowerCase();
40
- if (l.contains('google') || l.contains('gemini')) return const Color(0xFF4285F4);
41
- if (l.contains('openai') || l.contains('gpt')) return const Color(0xFF10A37F);
42
- if (l.contains('anthropic') || l.contains('claude')) return const Color(0xFFD97757);
43
- if (l.contains('meta') || l.contains('llama')) return const Color(0xFF0668E1);
44
- if (l.contains('mistral')) return const Color(0xFFF97316);
45
- return Colors.purpleAccent;
41
+ final value = model.provider.toLowerCase() + model.id.toLowerCase();
42
+ if (value.contains('google') || value.contains('gemini')) {
43
+ return const Color(0xFF4285F4);
44
+ }
45
+ if (value.contains('openai') || value.contains('gpt')) {
46
+ return const Color(0xFF10A37F);
47
+ }
48
+ if (value.contains('anthropic') || value.contains('claude')) {
49
+ return const Color(0xFFD97757);
50
+ }
51
+ if (value.contains('meta') || value.contains('llama')) {
52
+ return const Color(0xFF0668E1);
53
+ }
54
+ if (value.contains('mistral')) return const Color(0xFFF97316);
55
+ return const Color(0xFF7C8CFF);
46
56
  }
47
57
 
48
58
  IconData _getIconForModel(ModelMeta model) {
49
- final l = model.provider.toLowerCase() + model.id.toLowerCase();
50
- if (l.contains('google') || l.contains('gemini')) return Icons.auto_awesome;
51
- if (l.contains('openai') || l.contains('gpt')) return Icons.bolt;
52
- if (l.contains('anthropic') || l.contains('claude')) return Icons.library_books;
53
- if (l.contains('meta') || l.contains('llama')) return Icons.visibility_rounded;
59
+ final value = model.provider.toLowerCase() + model.id.toLowerCase();
60
+ if (value.contains('google') || value.contains('gemini')) {
61
+ return Icons.auto_awesome_rounded;
62
+ }
63
+ if (value.contains('openai') || value.contains('gpt')) {
64
+ return Icons.bolt_rounded;
65
+ }
66
+ if (value.contains('anthropic') || value.contains('claude')) {
67
+ return Icons.menu_book_rounded;
68
+ }
69
+ if (value.contains('meta') || value.contains('llama')) {
70
+ return Icons.visibility_rounded;
71
+ }
54
72
  return Icons.memory_rounded;
55
73
  }
56
74
 
57
75
  @override
58
76
  Widget build(BuildContext context) {
59
- final theme = Theme.of(context);
60
-
61
- return SafeArea(
62
- child: Padding(
63
- padding: const EdgeInsets.all(40),
64
- child: Column(
65
- crossAxisAlignment: CrossAxisAlignment.start,
66
- children: <Widget>[
67
- const SizedBox(height: 40),
68
- Text(
69
- 'Select your\nBrain',
70
- style: GoogleFonts.spaceGrotesk(
71
- fontSize: 48,
72
- fontWeight: FontWeight.w800,
73
- height: 1.1,
74
- letterSpacing: -1.5,
75
- color: Colors.white,
76
- ),
77
- ).animate()
78
- .fadeIn(duration: 600.ms)
79
- .slideX(begin: 0.1, end: 0, curve: Curves.easeOutCubic),
80
-
81
- const SizedBox(height: 16),
82
- Text(
83
- 'NeoOS works with the best LLMs. Choose the intelligence that powers your assistant.',
84
- style: TextStyle(
85
- fontSize: 18,
86
- color: Colors.white.withValues(alpha: 0.7),
87
- height: 1.5,
77
+ return OnboardingScaffold(
78
+ step: 3,
79
+ totalSteps: 4,
80
+ eyebrow: 'INTELLIGENCE',
81
+ title: 'Choose your\ndefault model.',
82
+ description: 'Pick the model NeoOS should use by default.',
83
+ footer: Row(
84
+ mainAxisAlignment: MainAxisAlignment.end,
85
+ children: <Widget>[
86
+ OnboardingPrimaryButton(
87
+ label: 'Finish setup',
88
+ icon: Icons.check_rounded,
89
+ onPressed: widget.onNext,
90
+ ),
91
+ ],
92
+ ),
93
+ child: _models.isEmpty
94
+ ? Center(
95
+ child: Text(
96
+ 'No available models found.\nYou can configure providers later in Settings.',
97
+ textAlign: TextAlign.center,
98
+ style: TextStyle(
99
+ color: Colors.white.withValues(alpha: 0.6),
100
+ fontSize: 16,
101
+ height: 1.5,
102
+ ),
88
103
  ),
89
- ).animate()
90
- .fadeIn(duration: 600.ms, delay: 200.ms)
91
- .slideX(begin: 0.1, end: 0, curve: Curves.easeOutCubic),
92
-
93
- const SizedBox(height: 60),
94
-
95
- Expanded(
96
- child: _models.isEmpty
97
- ? Center(
98
- child: Text(
99
- 'No available models found.\nYou can configure providers later in Settings.',
100
- textAlign: TextAlign.center,
101
- style: TextStyle(
102
- color: Colors.white.withValues(alpha: 0.5),
103
- fontSize: 16,
104
- ),
105
- ),
106
- ).animate().fadeIn()
107
- : ListView.separated(
108
- itemCount: _models.length,
109
- separatorBuilder: (context, index) => const SizedBox(height: 16),
110
- itemBuilder: (context, index) {
111
- final model = _models[index];
112
- final isSelected = _selectedModel == model.id;
113
-
114
- return _ModelCard(
115
- model: model,
116
- color: _getColorForModel(model),
117
- icon: _getIconForModel(model),
118
- isSelected: isSelected,
119
- onTap: () async {
120
- final previousModel = _selectedModel;
121
- setState(() {
122
- _selectedModel = model.id;
123
- });
124
- try {
125
- await widget.controller.saveSettingsPayload({'default_chat_model': model.id});
126
- } catch (e) {
127
- debugPrint('Failed to save model setting: $e');
128
- if (context.mounted) {
129
- ScaffoldMessenger.of(context).showSnackBar(
130
- SnackBar(
131
- content: Text('Failed to save selection: $e'),
132
- backgroundColor: Colors.redAccent,
133
- ),
104
+ )
105
+ : ListView.separated(
106
+ itemCount: _models.length,
107
+ separatorBuilder: (_, __) => const SizedBox(height: 14),
108
+ itemBuilder: (context, index) {
109
+ final model = _models[index];
110
+ final color = _getColorForModel(model);
111
+ final selected = _selectedModel == model.id;
112
+ return OnboardingOptionCard(
113
+ selected: selected,
114
+ accent: color,
115
+ onTap: () async {
116
+ final previousModel = _selectedModel;
117
+ setState(() => _selectedModel = model.id);
118
+ try {
119
+ await widget.controller.saveSettingsPayload(
120
+ <String, Object?>{'default_chat_model': model.id},
134
121
  );
135
- setState(() {
136
- _selectedModel = previousModel;
137
- });
122
+ } catch (e) {
123
+ if (context.mounted) {
124
+ ScaffoldMessenger.of(context).showSnackBar(
125
+ SnackBar(
126
+ content: Text('Failed to save selection: $e'),
127
+ ),
128
+ );
129
+ }
130
+ setState(() => _selectedModel = previousModel);
138
131
  }
139
- }
140
- },
141
- ).animate()
142
- .fadeIn(duration: 500.ms, delay: (400 + (index.clamp(0, 5)) * 100).ms)
143
- .slideY(begin: 0.2, end: 0, curve: Curves.easeOutCubic);
144
- },
145
- ),
146
- ),
147
-
148
- // Bottom Action
149
- Row(
150
- mainAxisAlignment: MainAxisAlignment.end,
151
- children: <Widget>[
152
- Container(
153
- decoration: BoxDecoration(
154
- boxShadow: <BoxShadow>[
155
- BoxShadow(
156
- color: theme.colorScheme.primary.withValues(alpha: 0.4),
157
- blurRadius: 30,
158
- offset: const Offset(0, 10),
159
- ),
160
- ],
161
- ),
162
- child: FilledButton.icon(
163
- onPressed: widget.onNext,
164
- style: FilledButton.styleFrom(
165
- padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
166
- shape: RoundedRectangleBorder(
167
- borderRadius: BorderRadius.circular(20),
168
- ),
169
- ),
170
- icon: const Icon(Icons.check_rounded),
171
- label: const Text(
172
- 'Finish Setup',
173
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
174
- ),
175
- ),
176
- ).animate().fadeIn(delay: 800.ms).slideY(begin: 0.5, end: 0),
177
- ],
178
- ),
179
- ],
180
- ),
181
- ),
182
- );
183
- }
184
- }
185
-
186
- class _ModelCard extends StatelessWidget {
187
- const _ModelCard({
188
- required this.model,
189
- required this.color,
190
- required this.icon,
191
- required this.isSelected,
192
- required this.onTap,
193
- });
194
-
195
- final ModelMeta model;
196
- final Color color;
197
- final IconData icon;
198
- final bool isSelected;
199
- final VoidCallback onTap;
200
-
201
- @override
202
- Widget build(BuildContext context) {
203
-
204
- return InkWell(
205
- onTap: onTap,
206
- borderRadius: BorderRadius.circular(24),
207
- child: AnimatedContainer(
208
- duration: const Duration(milliseconds: 300),
209
- curve: Curves.easeOutCubic,
210
- padding: const EdgeInsets.all(24),
211
- decoration: BoxDecoration(
212
- color: isSelected
213
- ? color.withValues(alpha: 0.15)
214
- : Colors.white.withValues(alpha: 0.05),
215
- borderRadius: BorderRadius.circular(24),
216
- border: Border.all(
217
- color: isSelected ? color : Colors.white.withValues(alpha: 0.1),
218
- width: isSelected ? 2 : 1,
219
- ),
220
- boxShadow: isSelected ? [
221
- BoxShadow(
222
- color: color.withValues(alpha: 0.15),
223
- blurRadius: 20,
224
- offset: const Offset(0, 8),
225
- )
226
- ] : [],
227
- ),
228
- child: Row(
229
- children: <Widget>[
230
- Container(
231
- padding: const EdgeInsets.all(16),
232
- decoration: BoxDecoration(
233
- color: color.withValues(alpha: 0.2),
234
- borderRadius: BorderRadius.circular(16),
235
- ),
236
- child: Icon(
237
- icon,
238
- color: color,
239
- size: 32,
240
- ),
241
- ),
242
- const SizedBox(width: 20),
243
- Expanded(
244
- child: Column(
245
- crossAxisAlignment: CrossAxisAlignment.start,
246
- mainAxisSize: MainAxisSize.min,
247
- children: [
248
- Row(
249
- children: [
250
- Text(
251
- model.label,
252
- style: const TextStyle(
253
- fontSize: 22,
254
- fontWeight: FontWeight.w700,
255
- color: Colors.white,
256
- ),
257
- ),
258
- const SizedBox(width: 8),
259
- Container(
260
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
261
- decoration: BoxDecoration(
262
- color: Colors.white.withValues(alpha: 0.1),
263
- borderRadius: BorderRadius.circular(8),
264
- ),
265
- child: Text(
266
- model.provider,
267
- style: TextStyle(
268
- fontSize: 12,
269
- fontWeight: FontWeight.w600,
270
- color: Colors.white.withValues(alpha: 0.7),
132
+ },
133
+ child: Row(
134
+ children: <Widget>[
135
+ Container(
136
+ width: 58,
137
+ height: 58,
138
+ decoration: BoxDecoration(
139
+ color: color.withValues(alpha: 0.18),
140
+ borderRadius: BorderRadius.circular(18),
141
+ ),
142
+ child: Icon(
143
+ _getIconForModel(model),
144
+ color: color,
145
+ size: 30,
146
+ ),
271
147
  ),
272
- ),
148
+ const SizedBox(width: 18),
149
+ Expanded(
150
+ child: Column(
151
+ crossAxisAlignment: CrossAxisAlignment.start,
152
+ children: <Widget>[
153
+ Wrap(
154
+ spacing: 10,
155
+ runSpacing: 8,
156
+ crossAxisAlignment: WrapCrossAlignment.center,
157
+ children: <Widget>[
158
+ Text(
159
+ model.label,
160
+ style: const TextStyle(
161
+ color: Colors.white,
162
+ fontSize: 20,
163
+ fontWeight: FontWeight.w800,
164
+ ),
165
+ ),
166
+ Container(
167
+ padding: const EdgeInsets.symmetric(
168
+ horizontal: 10,
169
+ vertical: 5,
170
+ ),
171
+ decoration: BoxDecoration(
172
+ color: Colors.white.withValues(
173
+ alpha: 0.08,
174
+ ),
175
+ borderRadius: BorderRadius.circular(
176
+ 999,
177
+ ),
178
+ ),
179
+ child: Text(
180
+ model.provider,
181
+ style: TextStyle(
182
+ color: Colors.white.withValues(
183
+ alpha: 0.72,
184
+ ),
185
+ fontSize: 12,
186
+ fontWeight: FontWeight.w700,
187
+ ),
188
+ ),
189
+ ),
190
+ ],
191
+ ),
192
+ const SizedBox(height: 6),
193
+ Text(
194
+ model.purpose,
195
+ style: TextStyle(
196
+ color: Colors.white.withValues(alpha: 0.68),
197
+ fontSize: 14,
198
+ height: 1.45,
199
+ ),
200
+ ),
201
+ ],
202
+ ),
203
+ ),
204
+ AnimatedSwitcher(
205
+ duration: const Duration(milliseconds: 220),
206
+ child: selected
207
+ ? Icon(
208
+ Icons.check_circle_rounded,
209
+ key: ValueKey<String>(model.id),
210
+ color: color,
211
+ size: 28,
212
+ )
213
+ : Icon(
214
+ Icons.radio_button_unchecked_rounded,
215
+ key: ValueKey<String>('idle-${model.id}'),
216
+ color: Colors.white.withValues(alpha: 0.25),
217
+ size: 28,
218
+ ),
219
+ ),
220
+ ],
273
221
  ),
274
- ],
275
- ),
276
- const SizedBox(height: 6),
277
- Text(
278
- model.purpose,
279
- style: TextStyle(
280
- fontSize: 14,
281
- color: Colors.white.withValues(alpha: 0.6),
282
- height: 1.4,
283
- ),
284
- ),
285
- ],
286
- ),
222
+ )
223
+ .animate()
224
+ .fadeIn(
225
+ duration: 420.ms,
226
+ delay: (180 + (index.clamp(0, 5) * 70)).ms,
227
+ )
228
+ .slideY(begin: 0.16, end: 0);
229
+ },
287
230
  ),
288
- if (isSelected) ...[
289
- const SizedBox(width: 16),
290
- Icon(Icons.check_circle_rounded, color: color, size: 28)
291
- .animate().scale(curve: Curves.elasticOut),
292
- ],
293
- ],
294
- ),
295
- ),
296
231
  );
297
232
  }
298
233
  }
@@ -40,7 +40,8 @@ class _OnboardingShellState extends State<OnboardingShell> {
40
40
  backgroundColor: Colors.black, // Deep black for video and transitions
41
41
  body: PageView(
42
42
  controller: _pageController,
43
- physics: const NeverScrollableScrollPhysics(), // Managed programmatically
43
+ physics:
44
+ const NeverScrollableScrollPhysics(), // Managed programmatically
44
45
  children: <Widget>[
45
46
  OnboardingVideoStep(onComplete: _nextStep),
46
47
  OnboardingWelcomeStep(onNext: _nextStep),
@@ -48,10 +49,7 @@ class _OnboardingShellState extends State<OnboardingShell> {
48
49
  onNext: _nextStep,
49
50
  controller: widget.controller,
50
51
  ),
51
- OnboardingModelStep(
52
- onNext: _finish,
53
- controller: widget.controller,
54
- ),
52
+ OnboardingModelStep(onNext: _finish, controller: widget.controller),
55
53
  ],
56
54
  ),
57
55
  );