neoagent 2.3.1-beta.60 → 2.3.1-beta.61

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.
@@ -104,12 +104,14 @@ class OnboardingOptionCard extends StatelessWidget {
104
104
  required this.selected,
105
105
  this.onTap,
106
106
  this.accent,
107
+ this.compact = false,
107
108
  });
108
109
 
109
110
  final Widget child;
110
111
  final bool selected;
111
112
  final VoidCallback? onTap;
112
113
  final Color? accent;
114
+ final bool compact;
113
115
 
114
116
  @override
115
117
  Widget build(BuildContext context) {
@@ -118,16 +120,16 @@ class OnboardingOptionCard extends StatelessWidget {
118
120
  color: Colors.transparent,
119
121
  child: InkWell(
120
122
  onTap: onTap,
121
- borderRadius: BorderRadius.circular(28),
123
+ borderRadius: BorderRadius.circular(compact ? 22 : 28),
122
124
  child: AnimatedContainer(
123
125
  duration: const Duration(milliseconds: 260),
124
126
  curve: Curves.easeOutCubic,
125
- padding: const EdgeInsets.all(22),
127
+ padding: EdgeInsets.all(compact ? 16 : 22),
126
128
  decoration: BoxDecoration(
127
129
  color: selected
128
130
  ? highlight.withValues(alpha: 0.16)
129
131
  : Colors.white.withValues(alpha: 0.055),
130
- borderRadius: BorderRadius.circular(28),
132
+ borderRadius: BorderRadius.circular(compact ? 22 : 28),
131
133
  border: Border.all(
132
134
  color: selected
133
135
  ? highlight.withValues(alpha: 0.92)
@@ -27,6 +27,9 @@ class _OnboardingMessagingStepState extends State<OnboardingMessagingStep> {
27
27
  final platforms = messagingPlatforms.length > 5
28
28
  ? messagingPlatforms.take(5).toList()
29
29
  : messagingPlatforms;
30
+ final width = MediaQuery.sizeOf(context).width;
31
+ final useGrid = width >= 700;
32
+ final columns = width >= 1050 ? 3 : (useGrid ? 2 : 1);
30
33
 
31
34
  return OnboardingScaffold(
32
35
  step: 2,
@@ -74,84 +77,199 @@ class _OnboardingMessagingStepState extends State<OnboardingMessagingStep> {
74
77
  ),
75
78
  ],
76
79
  ),
77
- child: ListView.separated(
78
- itemCount: platforms.length + 1,
79
- separatorBuilder: (_, __) => const SizedBox(height: 14),
80
- itemBuilder: (context, index) {
81
- if (index == platforms.length) {
82
- return const SizedBox.shrink();
83
- }
80
+ child: useGrid
81
+ ? GridView.builder(
82
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
83
+ crossAxisCount: columns,
84
+ crossAxisSpacing: 14,
85
+ mainAxisSpacing: 14,
86
+ childAspectRatio: width >= 1050 ? 1.5 : 1.35,
87
+ ),
88
+ itemCount: platforms.length,
89
+ itemBuilder: (context, index) {
90
+ final platform = platforms[index];
91
+ return _MessagingPlatformCard(
92
+ platform: platform,
93
+ selected: _selectedPlatform?.id == platform.id,
94
+ compact: true,
95
+ onTap: () => setState(() => _selectedPlatform = platform),
96
+ )
97
+ .animate()
98
+ .fadeIn(duration: 420.ms, delay: (180 + (index * 80)).ms)
99
+ .slideY(begin: 0.16, end: 0);
100
+ },
101
+ )
102
+ : ListView.separated(
103
+ itemCount: platforms.length,
104
+ separatorBuilder: (_, __) => const SizedBox(height: 14),
105
+ itemBuilder: (context, index) {
106
+ final platform = platforms[index];
107
+ return _MessagingPlatformCard(
108
+ platform: platform,
109
+ selected: _selectedPlatform?.id == platform.id,
110
+ onTap: () => setState(() => _selectedPlatform = platform),
111
+ )
112
+ .animate()
113
+ .fadeIn(duration: 420.ms, delay: (180 + (index * 80)).ms)
114
+ .slideY(begin: 0.16, end: 0);
115
+ },
116
+ ),
117
+ );
118
+ }
119
+ }
120
+
121
+ class _MessagingPlatformCard extends StatelessWidget {
122
+ const _MessagingPlatformCard({
123
+ required this.platform,
124
+ required this.selected,
125
+ required this.onTap,
126
+ this.compact = false,
127
+ });
128
+
129
+ final MessagingPlatformDescriptor platform;
130
+ final bool selected;
131
+ final VoidCallback onTap;
132
+ final bool compact;
84
133
 
85
- final platform = platforms[index];
86
- final selected = _selectedPlatform?.id == platform.id;
87
- return OnboardingOptionCard(
88
- selected: selected,
89
- accent: platform.accent,
90
- onTap: () => setState(() => _selectedPlatform = platform),
91
- child: Row(
134
+ @override
135
+ Widget build(BuildContext context) {
136
+ final iconSize = compact ? 24.0 : 30.0;
137
+ final shellSize = compact ? 48.0 : 58.0;
138
+ return OnboardingOptionCard(
139
+ selected: selected,
140
+ accent: platform.accent,
141
+ compact: compact,
142
+ onTap: onTap,
143
+ child: compact
144
+ ? Column(
145
+ crossAxisAlignment: CrossAxisAlignment.start,
146
+ children: <Widget>[
147
+ Row(
92
148
  children: <Widget>[
93
149
  Container(
94
- width: 58,
95
- height: 58,
150
+ width: shellSize,
151
+ height: shellSize,
96
152
  decoration: BoxDecoration(
97
153
  color: platform.accent.withValues(alpha: 0.18),
98
- borderRadius: BorderRadius.circular(18),
154
+ borderRadius: BorderRadius.circular(16),
99
155
  ),
100
156
  child: Icon(
101
157
  platform.icon,
102
158
  color: platform.accent,
103
- size: 30,
104
- ),
105
- ),
106
- const SizedBox(width: 18),
107
- Expanded(
108
- child: Column(
109
- crossAxisAlignment: CrossAxisAlignment.start,
110
- children: <Widget>[
111
- Text(
112
- platform.label,
113
- style: const TextStyle(
114
- color: Colors.white,
115
- fontSize: 20,
116
- fontWeight: FontWeight.w800,
117
- ),
118
- ),
119
- const SizedBox(height: 5),
120
- Text(
121
- platform.subtitle,
122
- style: TextStyle(
123
- color: Colors.white.withValues(alpha: 0.68),
124
- fontSize: 14,
125
- height: 1.45,
126
- ),
127
- ),
128
- ],
159
+ size: iconSize,
129
160
  ),
130
161
  ),
131
- AnimatedSwitcher(
132
- duration: const Duration(milliseconds: 220),
133
- child: selected
134
- ? Icon(
135
- Icons.check_circle_rounded,
136
- key: ValueKey<String>(platform.id),
137
- color: platform.accent,
138
- size: 28,
139
- )
140
- : Icon(
141
- Icons.add_circle_outline_rounded,
142
- key: ValueKey<String>('idle-${platform.id}'),
143
- color: Colors.white.withValues(alpha: 0.26),
144
- size: 28,
145
- ),
162
+ const Spacer(),
163
+ _SelectionIcon(
164
+ selected: selected,
165
+ color: platform.accent,
166
+ id: platform.id,
146
167
  ),
147
168
  ],
148
169
  ),
149
- )
150
- .animate()
151
- .fadeIn(duration: 420.ms, delay: (180 + (index * 80)).ms)
152
- .slideY(begin: 0.16, end: 0);
153
- },
154
- ),
170
+ const SizedBox(height: 14),
171
+ Text(
172
+ platform.label,
173
+ maxLines: 1,
174
+ overflow: TextOverflow.ellipsis,
175
+ style: const TextStyle(
176
+ color: Colors.white,
177
+ fontSize: 18,
178
+ fontWeight: FontWeight.w800,
179
+ ),
180
+ ),
181
+ const SizedBox(height: 4),
182
+ Text(
183
+ platform.subtitle,
184
+ maxLines: 2,
185
+ overflow: TextOverflow.ellipsis,
186
+ style: TextStyle(
187
+ color: Colors.white.withValues(alpha: 0.68),
188
+ fontSize: 13,
189
+ height: 1.35,
190
+ ),
191
+ ),
192
+ ],
193
+ )
194
+ : Row(
195
+ children: <Widget>[
196
+ Container(
197
+ width: shellSize,
198
+ height: shellSize,
199
+ decoration: BoxDecoration(
200
+ color: platform.accent.withValues(alpha: 0.18),
201
+ borderRadius: BorderRadius.circular(18),
202
+ ),
203
+ child: Icon(
204
+ platform.icon,
205
+ color: platform.accent,
206
+ size: iconSize,
207
+ ),
208
+ ),
209
+ const SizedBox(width: 18),
210
+ Expanded(
211
+ child: Column(
212
+ crossAxisAlignment: CrossAxisAlignment.start,
213
+ children: <Widget>[
214
+ Text(
215
+ platform.label,
216
+ style: const TextStyle(
217
+ color: Colors.white,
218
+ fontSize: 20,
219
+ fontWeight: FontWeight.w800,
220
+ ),
221
+ ),
222
+ const SizedBox(height: 5),
223
+ Text(
224
+ platform.subtitle,
225
+ style: TextStyle(
226
+ color: Colors.white.withValues(alpha: 0.68),
227
+ fontSize: 14,
228
+ height: 1.45,
229
+ ),
230
+ ),
231
+ ],
232
+ ),
233
+ ),
234
+ _SelectionIcon(
235
+ selected: selected,
236
+ color: platform.accent,
237
+ id: platform.id,
238
+ ),
239
+ ],
240
+ ),
241
+ );
242
+ }
243
+ }
244
+
245
+ class _SelectionIcon extends StatelessWidget {
246
+ const _SelectionIcon({
247
+ required this.selected,
248
+ required this.color,
249
+ required this.id,
250
+ });
251
+
252
+ final bool selected;
253
+ final Color color;
254
+ final String id;
255
+
256
+ @override
257
+ Widget build(BuildContext context) {
258
+ return AnimatedSwitcher(
259
+ duration: const Duration(milliseconds: 220),
260
+ child: selected
261
+ ? Icon(
262
+ Icons.check_circle_rounded,
263
+ key: ValueKey<String>(id),
264
+ color: color,
265
+ size: 28,
266
+ )
267
+ : Icon(
268
+ Icons.add_circle_outline_rounded,
269
+ key: ValueKey<String>('idle-$id'),
270
+ color: Colors.white.withValues(alpha: 0.26),
271
+ size: 28,
272
+ ),
155
273
  );
156
274
  }
157
275
  }
@@ -74,6 +74,10 @@ class _OnboardingModelStepState extends State<OnboardingModelStep> {
74
74
 
75
75
  @override
76
76
  Widget build(BuildContext context) {
77
+ final width = MediaQuery.sizeOf(context).width;
78
+ final useGrid = width >= 720;
79
+ final columns = width >= 1150 ? 3 : (useGrid ? 2 : 1);
80
+
77
81
  return OnboardingScaffold(
78
82
  step: 3,
79
83
  totalSteps: 4,
@@ -102,123 +106,44 @@ class _OnboardingModelStepState extends State<OnboardingModelStep> {
102
106
  ),
103
107
  ),
104
108
  )
109
+ : useGrid
110
+ ? GridView.builder(
111
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
112
+ crossAxisCount: columns,
113
+ crossAxisSpacing: 14,
114
+ mainAxisSpacing: 14,
115
+ childAspectRatio: width >= 1150 ? 1.38 : 1.18,
116
+ ),
117
+ itemCount: _models.length,
118
+ itemBuilder: (context, index) {
119
+ final model = _models[index];
120
+ return _ModelChoiceCard(
121
+ model: model,
122
+ color: _getColorForModel(model),
123
+ icon: _getIconForModel(model),
124
+ selected: _selectedModel == model.id,
125
+ compact: true,
126
+ onTap: () => _selectModel(context, model),
127
+ )
128
+ .animate()
129
+ .fadeIn(
130
+ duration: 420.ms,
131
+ delay: (180 + (index.clamp(0, 5) * 70)).ms,
132
+ )
133
+ .slideY(begin: 0.16, end: 0);
134
+ },
135
+ )
105
136
  : ListView.separated(
106
137
  itemCount: _models.length,
107
138
  separatorBuilder: (_, __) => const SizedBox(height: 14),
108
139
  itemBuilder: (context, index) {
109
140
  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},
121
- );
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);
131
- }
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
- ),
147
- ),
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
- ],
221
- ),
141
+ return _ModelChoiceCard(
142
+ model: model,
143
+ color: _getColorForModel(model),
144
+ icon: _getIconForModel(model),
145
+ selected: _selectedModel == model.id,
146
+ onTap: () => _selectModel(context, model),
222
147
  )
223
148
  .animate()
224
149
  .fadeIn(
@@ -230,4 +155,212 @@ class _OnboardingModelStepState extends State<OnboardingModelStep> {
230
155
  ),
231
156
  );
232
157
  }
158
+
159
+ Future<void> _selectModel(BuildContext context, ModelMeta model) async {
160
+ final previousModel = _selectedModel;
161
+ setState(() => _selectedModel = model.id);
162
+ try {
163
+ await widget.controller.saveSettingsPayload(<String, Object?>{
164
+ 'default_chat_model': model.id,
165
+ });
166
+ } catch (e) {
167
+ if (context.mounted) {
168
+ ScaffoldMessenger.of(
169
+ context,
170
+ ).showSnackBar(SnackBar(content: Text('Failed to save selection: $e')));
171
+ }
172
+ setState(() => _selectedModel = previousModel);
173
+ }
174
+ }
175
+ }
176
+
177
+ class _ModelChoiceCard extends StatelessWidget {
178
+ const _ModelChoiceCard({
179
+ required this.model,
180
+ required this.color,
181
+ required this.icon,
182
+ required this.selected,
183
+ required this.onTap,
184
+ this.compact = false,
185
+ });
186
+
187
+ final ModelMeta model;
188
+ final Color color;
189
+ final IconData icon;
190
+ final bool selected;
191
+ final VoidCallback onTap;
192
+ final bool compact;
193
+
194
+ @override
195
+ Widget build(BuildContext context) {
196
+ final shellSize = compact ? 48.0 : 58.0;
197
+ final iconSize = compact ? 24.0 : 30.0;
198
+ final titleSize = compact ? 17.0 : 20.0;
199
+ final purposeSize = compact ? 13.0 : 14.0;
200
+
201
+ return OnboardingOptionCard(
202
+ selected: selected,
203
+ accent: color,
204
+ compact: compact,
205
+ onTap: onTap,
206
+ child: compact
207
+ ? Column(
208
+ crossAxisAlignment: CrossAxisAlignment.start,
209
+ children: <Widget>[
210
+ Row(
211
+ children: <Widget>[
212
+ Container(
213
+ width: shellSize,
214
+ height: shellSize,
215
+ decoration: BoxDecoration(
216
+ color: color.withValues(alpha: 0.18),
217
+ borderRadius: BorderRadius.circular(16),
218
+ ),
219
+ child: Icon(icon, color: color, size: iconSize),
220
+ ),
221
+ const Spacer(),
222
+ AnimatedSwitcher(
223
+ duration: const Duration(milliseconds: 220),
224
+ child: selected
225
+ ? Icon(
226
+ Icons.check_circle_rounded,
227
+ key: ValueKey<String>(model.id),
228
+ color: color,
229
+ size: 28,
230
+ )
231
+ : Icon(
232
+ Icons.radio_button_unchecked_rounded,
233
+ key: ValueKey<String>('idle-${model.id}'),
234
+ color: Colors.white.withValues(alpha: 0.25),
235
+ size: 28,
236
+ ),
237
+ ),
238
+ ],
239
+ ),
240
+ const SizedBox(height: 12),
241
+ Text(
242
+ model.label,
243
+ maxLines: 1,
244
+ overflow: TextOverflow.ellipsis,
245
+ style: TextStyle(
246
+ color: Colors.white,
247
+ fontSize: titleSize,
248
+ fontWeight: FontWeight.w800,
249
+ ),
250
+ ),
251
+ const SizedBox(height: 6),
252
+ Container(
253
+ padding: const EdgeInsets.symmetric(
254
+ horizontal: 10,
255
+ vertical: 5,
256
+ ),
257
+ decoration: BoxDecoration(
258
+ color: Colors.white.withValues(alpha: 0.08),
259
+ borderRadius: BorderRadius.circular(999),
260
+ ),
261
+ child: Text(
262
+ model.provider,
263
+ maxLines: 1,
264
+ overflow: TextOverflow.ellipsis,
265
+ style: TextStyle(
266
+ color: Colors.white.withValues(alpha: 0.72),
267
+ fontSize: 11,
268
+ fontWeight: FontWeight.w700,
269
+ ),
270
+ ),
271
+ ),
272
+ const SizedBox(height: 10),
273
+ Text(
274
+ model.purpose,
275
+ maxLines: 3,
276
+ overflow: TextOverflow.ellipsis,
277
+ style: TextStyle(
278
+ color: Colors.white.withValues(alpha: 0.68),
279
+ fontSize: purposeSize,
280
+ height: 1.35,
281
+ ),
282
+ ),
283
+ ],
284
+ )
285
+ : Row(
286
+ children: <Widget>[
287
+ Container(
288
+ width: shellSize,
289
+ height: shellSize,
290
+ decoration: BoxDecoration(
291
+ color: color.withValues(alpha: 0.18),
292
+ borderRadius: BorderRadius.circular(18),
293
+ ),
294
+ child: Icon(icon, color: color, size: iconSize),
295
+ ),
296
+ const SizedBox(width: 18),
297
+ Expanded(
298
+ child: Column(
299
+ crossAxisAlignment: CrossAxisAlignment.start,
300
+ children: <Widget>[
301
+ Wrap(
302
+ spacing: 10,
303
+ runSpacing: 8,
304
+ crossAxisAlignment: WrapCrossAlignment.center,
305
+ children: <Widget>[
306
+ Text(
307
+ model.label,
308
+ style: TextStyle(
309
+ color: Colors.white,
310
+ fontSize: titleSize,
311
+ fontWeight: FontWeight.w800,
312
+ ),
313
+ ),
314
+ Container(
315
+ padding: const EdgeInsets.symmetric(
316
+ horizontal: 10,
317
+ vertical: 5,
318
+ ),
319
+ decoration: BoxDecoration(
320
+ color: Colors.white.withValues(alpha: 0.08),
321
+ borderRadius: BorderRadius.circular(999),
322
+ ),
323
+ child: Text(
324
+ model.provider,
325
+ style: TextStyle(
326
+ color: Colors.white.withValues(alpha: 0.72),
327
+ fontSize: 12,
328
+ fontWeight: FontWeight.w700,
329
+ ),
330
+ ),
331
+ ),
332
+ ],
333
+ ),
334
+ const SizedBox(height: 6),
335
+ Text(
336
+ model.purpose,
337
+ style: TextStyle(
338
+ color: Colors.white.withValues(alpha: 0.68),
339
+ fontSize: purposeSize,
340
+ height: 1.45,
341
+ ),
342
+ ),
343
+ ],
344
+ ),
345
+ ),
346
+ AnimatedSwitcher(
347
+ duration: const Duration(milliseconds: 220),
348
+ child: selected
349
+ ? Icon(
350
+ Icons.check_circle_rounded,
351
+ key: ValueKey<String>(model.id),
352
+ color: color,
353
+ size: 28,
354
+ )
355
+ : Icon(
356
+ Icons.radio_button_unchecked_rounded,
357
+ key: ValueKey<String>('idle-${model.id}'),
358
+ color: Colors.white.withValues(alpha: 0.25),
359
+ size: 28,
360
+ ),
361
+ ),
362
+ ],
363
+ ),
364
+ );
365
+ }
233
366
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.60",
3
+ "version": "2.3.1-beta.61",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",