neoagent 2.3.1-beta.62 → 2.3.1-beta.64
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.
- package/.env.example +9 -0
- package/docs/configuration.md +11 -0
- package/flutter_app/lib/main.dart +2 -0
- package/flutter_app/lib/main_account_settings.dart +50 -22
- package/flutter_app/lib/main_admin.dart +24 -10
- package/flutter_app/lib/main_app_shell.dart +10 -9
- package/flutter_app/lib/main_chat.dart +433 -309
- package/flutter_app/lib/main_controller.dart +164 -6
- package/flutter_app/lib/main_integrations.dart +15 -7
- package/flutter_app/lib/main_models.dart +116 -7
- package/flutter_app/lib/main_navigation.dart +27 -18
- package/flutter_app/lib/main_operations.dart +162 -91
- package/flutter_app/lib/main_runtime.dart +22 -0
- package/flutter_app/lib/main_settings.dart +287 -75
- package/flutter_app/lib/main_shared.dart +165 -6
- package/flutter_app/lib/main_unified.dart +388 -0
- package/flutter_app/lib/src/analytics_service.dart +294 -0
- package/flutter_app/lib/src/backend_client.dart +4 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +2 -0
- package/flutter_app/pubspec.lock +8 -0
- package/flutter_app/pubspec.yaml +1 -0
- package/flutter_app/web/index.html +1 -0
- package/package.json +1 -1
- package/server/config/analytics.js +30 -0
- package/server/db/database.js +52 -0
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/NOTICES +183 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/packages/mixpanel_flutter/assets/mixpanel.js +3 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/index.html +1 -0
- package/server/public/main.dart.js +83685 -82046
- package/server/routes/agents.js +2 -1
- package/server/routes/memory.js +75 -3
- package/server/routes/runtime.js +14 -0
- package/server/routes/widgets.js +4 -4
- package/server/services/ai/engine.js +86 -0
- package/server/services/ai/runEvents.js +100 -0
- package/server/services/memory/manager.js +242 -26
- package/server/services/websocket.js +3 -1
- package/server/services/widgets/focus_widget.js +126 -0
- package/server/services/widgets/service.js +130 -2
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
part of 'main.dart';
|
|
2
2
|
|
|
3
3
|
class SettingsPanel extends StatefulWidget {
|
|
4
|
-
const SettingsPanel({
|
|
4
|
+
const SettingsPanel({
|
|
5
|
+
super.key,
|
|
6
|
+
required this.controller,
|
|
7
|
+
this.embedded = false,
|
|
8
|
+
});
|
|
5
9
|
|
|
6
10
|
final NeoAgentController controller;
|
|
11
|
+
final bool embedded;
|
|
7
12
|
|
|
8
13
|
@override
|
|
9
14
|
State<SettingsPanel> createState() => _SettingsPanelState();
|
|
@@ -69,7 +74,99 @@ const Map<String, List<String>> _voiceLiveVoicesByProvider =
|
|
|
69
74
|
],
|
|
70
75
|
};
|
|
71
76
|
|
|
77
|
+
class _SettingsSection {
|
|
78
|
+
const _SettingsSection(
|
|
79
|
+
this.title,
|
|
80
|
+
this.keywords, {
|
|
81
|
+
this.requiresDesktop = false,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
final String title;
|
|
85
|
+
final List<String> keywords;
|
|
86
|
+
final bool requiresDesktop;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const _overviewSettingsSection = _SettingsSection('overview', <String>[
|
|
90
|
+
'overview',
|
|
91
|
+
'summary',
|
|
92
|
+
'onboarding',
|
|
93
|
+
'platform',
|
|
94
|
+
'providers',
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
const _workspaceSettingsSection = _SettingsSection('workspace', <String>[
|
|
98
|
+
'workspace',
|
|
99
|
+
'browser',
|
|
100
|
+
'extension',
|
|
101
|
+
'headless',
|
|
102
|
+
'routing',
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const _modelsSettingsSection = _SettingsSection('models', <String>[
|
|
106
|
+
'models',
|
|
107
|
+
'providers',
|
|
108
|
+
'routing',
|
|
109
|
+
'fallback',
|
|
110
|
+
'chat',
|
|
111
|
+
'sub-agent',
|
|
112
|
+
'subagent',
|
|
113
|
+
'smart selector',
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
const _voiceRecordingSettingsSection = _SettingsSection(
|
|
117
|
+
'voice recording',
|
|
118
|
+
<String>[
|
|
119
|
+
'voice',
|
|
120
|
+
'recording',
|
|
121
|
+
'transcription',
|
|
122
|
+
'summary',
|
|
123
|
+
'speech',
|
|
124
|
+
'tts',
|
|
125
|
+
'stt',
|
|
126
|
+
'live',
|
|
127
|
+
],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const _desktopSettingsSection = _SettingsSection('desktop', <String>[
|
|
131
|
+
'desktop',
|
|
132
|
+
'permissions',
|
|
133
|
+
'capture',
|
|
134
|
+
'companion',
|
|
135
|
+
'screen recording',
|
|
136
|
+
'accessibility',
|
|
137
|
+
'input',
|
|
138
|
+
], requiresDesktop: true);
|
|
139
|
+
|
|
140
|
+
const _updatesSettingsSection = _SettingsSection('updates', <String>[
|
|
141
|
+
'updates',
|
|
142
|
+
'release',
|
|
143
|
+
'channel',
|
|
144
|
+
'version',
|
|
145
|
+
'self update',
|
|
146
|
+
'upgrade',
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
const _diagnosticsSettingsSection = _SettingsSection('diagnostics', <String>[
|
|
150
|
+
'diagnostics',
|
|
151
|
+
'logs',
|
|
152
|
+
'token',
|
|
153
|
+
'usage',
|
|
154
|
+
'debug',
|
|
155
|
+
'health',
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
const List<_SettingsSection> _settingsSearchSections = <_SettingsSection>[
|
|
159
|
+
_overviewSettingsSection,
|
|
160
|
+
_workspaceSettingsSection,
|
|
161
|
+
_modelsSettingsSection,
|
|
162
|
+
_voiceRecordingSettingsSection,
|
|
163
|
+
_desktopSettingsSection,
|
|
164
|
+
_updatesSettingsSection,
|
|
165
|
+
_diagnosticsSettingsSection,
|
|
166
|
+
];
|
|
167
|
+
|
|
72
168
|
class _SettingsPanelState extends State<SettingsPanel> {
|
|
169
|
+
late final TextEditingController _searchController;
|
|
73
170
|
late bool _headlessBrowser;
|
|
74
171
|
late String _browserBackend;
|
|
75
172
|
late bool _smarterSelector;
|
|
@@ -91,11 +188,13 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
91
188
|
@override
|
|
92
189
|
void initState() {
|
|
93
190
|
super.initState();
|
|
191
|
+
_searchController = TextEditingController();
|
|
94
192
|
_hydrate();
|
|
95
193
|
}
|
|
96
194
|
|
|
97
195
|
@override
|
|
98
196
|
void dispose() {
|
|
197
|
+
_searchController.dispose();
|
|
99
198
|
for (final controller in _providerBaseUrlControllers.values) {
|
|
100
199
|
controller.dispose();
|
|
101
200
|
}
|
|
@@ -186,6 +285,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
186
285
|
@override
|
|
187
286
|
Widget build(BuildContext context) {
|
|
188
287
|
final controller = widget.controller;
|
|
288
|
+
final searchQuery = _searchController.text.trim().toLowerCase();
|
|
189
289
|
final availableModels = controller.supportedModels
|
|
190
290
|
.where((model) => model.available)
|
|
191
291
|
.toList();
|
|
@@ -195,103 +295,210 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
195
295
|
final modelChoices = <DropdownMenuItem<String>>[
|
|
196
296
|
const DropdownMenuItem<String>(
|
|
197
297
|
value: 'auto',
|
|
198
|
-
child: Text(
|
|
298
|
+
child: Text(
|
|
299
|
+
'Smart Selector (Auto)',
|
|
300
|
+
maxLines: 1,
|
|
301
|
+
overflow: TextOverflow.ellipsis,
|
|
302
|
+
),
|
|
199
303
|
),
|
|
200
304
|
...routingModels.map(
|
|
201
|
-
(model) =>
|
|
202
|
-
|
|
305
|
+
(model) => DropdownMenuItem<String>(
|
|
306
|
+
value: model.id,
|
|
307
|
+
child: Text(
|
|
308
|
+
model.label,
|
|
309
|
+
maxLines: 1,
|
|
310
|
+
overflow: TextOverflow.ellipsis,
|
|
311
|
+
),
|
|
312
|
+
),
|
|
203
313
|
),
|
|
204
314
|
];
|
|
205
315
|
final enabledSmartModels = _enabledModels
|
|
206
316
|
.where((id) => routingModels.any((model) => model.id == id))
|
|
207
317
|
.length;
|
|
318
|
+
final visibleSearchSections = _settingsSearchSections
|
|
319
|
+
.where((section) => !section.requiresDesktop || _supportsDesktopShell)
|
|
320
|
+
.toSet();
|
|
208
321
|
|
|
209
322
|
return ListView(
|
|
210
|
-
padding: _pagePadding(context),
|
|
323
|
+
padding: widget.embedded ? EdgeInsets.zero : _pagePadding(context),
|
|
211
324
|
children: <Widget>[
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
defaultChatModel: _defaultChatModel,
|
|
227
|
-
defaultSubagentModel: _defaultSubagentModel,
|
|
228
|
-
defaultRecordingTranscriptionProvider: 'deepgram',
|
|
229
|
-
defaultRecordingTranscriptionModel:
|
|
230
|
-
_defaultRecordingTranscriptionModel,
|
|
231
|
-
defaultRecordingSummaryProvider: _providerForSelectedModel(
|
|
232
|
-
_defaultRecordingSummaryModel,
|
|
233
|
-
controller.supportedModels,
|
|
234
|
-
),
|
|
235
|
-
defaultRecordingSummaryModel: _defaultRecordingSummaryModel,
|
|
236
|
-
fallbackModel: _fallbackModel,
|
|
237
|
-
defaultSpeechModel: _defaultSpeechModel,
|
|
238
|
-
voiceSttProvider: controller.voiceSttProvider,
|
|
239
|
-
voiceSttModel: controller.voiceSttModel,
|
|
240
|
-
voiceTtsProvider: controller.voiceTtsProvider,
|
|
241
|
-
voiceTtsModel: controller.voiceTtsModel,
|
|
242
|
-
voiceTtsVoice: controller.voiceTtsVoice,
|
|
243
|
-
voiceRuntimeMode: 'live',
|
|
244
|
-
voiceLiveProvider: _voiceLiveProvider,
|
|
245
|
-
voiceLiveModel: _voiceLiveModel,
|
|
246
|
-
voiceLiveVoice: _voiceLiveVoice,
|
|
247
|
-
aiProviderConfigs: _buildProviderPayload(),
|
|
248
|
-
),
|
|
249
|
-
style: FilledButton.styleFrom(backgroundColor: _accent),
|
|
250
|
-
icon: controller.isSavingSettings
|
|
251
|
-
? const SizedBox.square(
|
|
252
|
-
dimension: 16,
|
|
253
|
-
child: CircularProgressIndicator(
|
|
254
|
-
strokeWidth: 2,
|
|
255
|
-
color: Colors.white,
|
|
256
|
-
),
|
|
257
|
-
)
|
|
258
|
-
: Icon(Icons.save_outlined),
|
|
259
|
-
label: Text('Save'),
|
|
325
|
+
if (!widget.embedded)
|
|
326
|
+
_PageTitle(
|
|
327
|
+
title: 'Settings',
|
|
328
|
+
subtitle:
|
|
329
|
+
'Workspace, models, recording, update, and diagnostics controls.',
|
|
330
|
+
trailing: _settingsSaveButton(controller),
|
|
331
|
+
)
|
|
332
|
+
else
|
|
333
|
+
Align(
|
|
334
|
+
alignment: Alignment.centerRight,
|
|
335
|
+
child: Padding(
|
|
336
|
+
padding: const EdgeInsets.only(bottom: 12),
|
|
337
|
+
child: _settingsSaveButton(controller),
|
|
338
|
+
),
|
|
260
339
|
),
|
|
261
|
-
),
|
|
262
340
|
if (controller.errorMessage != null) ...<Widget>[
|
|
263
341
|
_InlineError(message: controller.errorMessage!),
|
|
264
342
|
const SizedBox(height: 16),
|
|
265
343
|
],
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
344
|
+
TextField(
|
|
345
|
+
controller: _searchController,
|
|
346
|
+
onChanged: (_) => setState(() {}),
|
|
347
|
+
decoration: InputDecoration(
|
|
348
|
+
labelText: 'Search settings',
|
|
349
|
+
hintText: 'Models, browser, voice, updates, diagnostics...',
|
|
350
|
+
prefixIcon: const Icon(Icons.search),
|
|
351
|
+
suffixIcon: searchQuery.isEmpty
|
|
352
|
+
? null
|
|
353
|
+
: IconButton(
|
|
354
|
+
onPressed: () {
|
|
355
|
+
_searchController.clear();
|
|
356
|
+
setState(() {});
|
|
357
|
+
},
|
|
358
|
+
icon: const Icon(Icons.close),
|
|
359
|
+
),
|
|
360
|
+
),
|
|
282
361
|
),
|
|
283
362
|
const SizedBox(height: 16),
|
|
284
|
-
if (
|
|
363
|
+
if (_matchesSettingsSection(
|
|
364
|
+
searchQuery,
|
|
365
|
+
_overviewSettingsSection,
|
|
366
|
+
)) ...<Widget>[
|
|
367
|
+
_buildSettingsOverview(controller, availableModels.length),
|
|
368
|
+
const SizedBox(height: 16),
|
|
369
|
+
],
|
|
370
|
+
if (_matchesSettingsSection(
|
|
371
|
+
searchQuery,
|
|
372
|
+
_workspaceSettingsSection,
|
|
373
|
+
)) ...<Widget>[
|
|
374
|
+
_buildWorkspaceSection(controller),
|
|
375
|
+
const SizedBox(height: 16),
|
|
376
|
+
],
|
|
377
|
+
if (_matchesSettingsSection(
|
|
378
|
+
searchQuery,
|
|
379
|
+
_modelsSettingsSection,
|
|
380
|
+
)) ...<Widget>[
|
|
381
|
+
_buildModelsSection(
|
|
382
|
+
controller: controller,
|
|
383
|
+
modelChoices: modelChoices,
|
|
384
|
+
routingModels: routingModels,
|
|
385
|
+
availableModels: availableModels,
|
|
386
|
+
enabledSmartModels: enabledSmartModels,
|
|
387
|
+
),
|
|
388
|
+
const SizedBox(height: 16),
|
|
389
|
+
],
|
|
390
|
+
if (_matchesSettingsSection(
|
|
391
|
+
searchQuery,
|
|
392
|
+
_voiceRecordingSettingsSection,
|
|
393
|
+
)) ...<Widget>[
|
|
394
|
+
_buildVoiceAndRecordingSection(
|
|
395
|
+
controller: controller,
|
|
396
|
+
modelChoices: modelChoices,
|
|
397
|
+
routingModels: routingModels,
|
|
398
|
+
),
|
|
399
|
+
const SizedBox(height: 16),
|
|
400
|
+
],
|
|
401
|
+
if (visibleSearchSections.contains(_desktopSettingsSection) &&
|
|
402
|
+
_matchesSettingsSection(
|
|
403
|
+
searchQuery,
|
|
404
|
+
_desktopSettingsSection,
|
|
405
|
+
)) ...<Widget>[
|
|
285
406
|
_buildDesktopSection(controller),
|
|
286
407
|
const SizedBox(height: 16),
|
|
287
408
|
],
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
409
|
+
if (_matchesSettingsSection(
|
|
410
|
+
searchQuery,
|
|
411
|
+
_updatesSettingsSection,
|
|
412
|
+
)) ...<Widget>[
|
|
413
|
+
_buildUpdatesSection(controller),
|
|
414
|
+
const SizedBox(height: 16),
|
|
415
|
+
],
|
|
416
|
+
if (_matchesSettingsSection(
|
|
417
|
+
searchQuery,
|
|
418
|
+
_diagnosticsSettingsSection,
|
|
419
|
+
)) ...<Widget>[_buildDiagnosticsSection(controller)],
|
|
420
|
+
if (_noSettingsMatches(searchQuery, visibleSearchSections)) ...<Widget>[
|
|
421
|
+
const _EmptyCard(
|
|
422
|
+
title: 'No matching settings',
|
|
423
|
+
subtitle:
|
|
424
|
+
'Try a broader search like models, browser, voice, or updates.',
|
|
425
|
+
),
|
|
426
|
+
],
|
|
291
427
|
],
|
|
292
428
|
);
|
|
293
429
|
}
|
|
294
430
|
|
|
431
|
+
bool _matchesSettingsSection(String query, _SettingsSection section) {
|
|
432
|
+
if (query.isEmpty) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
final haystack = <String>[
|
|
436
|
+
section.title,
|
|
437
|
+
...section.keywords,
|
|
438
|
+
].join(' ').toLowerCase();
|
|
439
|
+
return haystack.contains(query);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
bool _noSettingsMatches(
|
|
443
|
+
String query,
|
|
444
|
+
Iterable<_SettingsSection> visibleSections,
|
|
445
|
+
) {
|
|
446
|
+
if (query.isEmpty) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
return !visibleSections.any(
|
|
450
|
+
(section) => _matchesSettingsSection(query, section),
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
Widget _settingsSaveButton(NeoAgentController controller) {
|
|
455
|
+
return FilledButton.icon(
|
|
456
|
+
onPressed: controller.isSavingSettings
|
|
457
|
+
? null
|
|
458
|
+
: () => controller.saveSettings(
|
|
459
|
+
headlessBrowser: _headlessBrowser,
|
|
460
|
+
browserBackend: _browserBackend == 'extension'
|
|
461
|
+
? 'extension'
|
|
462
|
+
: controller.cloudBrowserBackend,
|
|
463
|
+
smarterSelector: _smarterSelector,
|
|
464
|
+
enabledModels: _enabledModels.toList(),
|
|
465
|
+
defaultChatModel: _defaultChatModel,
|
|
466
|
+
defaultSubagentModel: _defaultSubagentModel,
|
|
467
|
+
defaultRecordingTranscriptionProvider: 'deepgram',
|
|
468
|
+
defaultRecordingTranscriptionModel:
|
|
469
|
+
_defaultRecordingTranscriptionModel,
|
|
470
|
+
defaultRecordingSummaryProvider: _providerForSelectedModel(
|
|
471
|
+
_defaultRecordingSummaryModel,
|
|
472
|
+
controller.supportedModels,
|
|
473
|
+
),
|
|
474
|
+
defaultRecordingSummaryModel: _defaultRecordingSummaryModel,
|
|
475
|
+
fallbackModel: _fallbackModel,
|
|
476
|
+
defaultSpeechModel: _defaultSpeechModel,
|
|
477
|
+
voiceSttProvider: controller.voiceSttProvider,
|
|
478
|
+
voiceSttModel: controller.voiceSttModel,
|
|
479
|
+
voiceTtsProvider: controller.voiceTtsProvider,
|
|
480
|
+
voiceTtsModel: controller.voiceTtsModel,
|
|
481
|
+
voiceTtsVoice: controller.voiceTtsVoice,
|
|
482
|
+
voiceRuntimeMode: 'live',
|
|
483
|
+
voiceLiveProvider: _voiceLiveProvider,
|
|
484
|
+
voiceLiveModel: _voiceLiveModel,
|
|
485
|
+
voiceLiveVoice: _voiceLiveVoice,
|
|
486
|
+
aiProviderConfigs: _buildProviderPayload(),
|
|
487
|
+
),
|
|
488
|
+
style: FilledButton.styleFrom(backgroundColor: _accent),
|
|
489
|
+
icon: controller.isSavingSettings
|
|
490
|
+
? const SizedBox.square(
|
|
491
|
+
dimension: 16,
|
|
492
|
+
child: CircularProgressIndicator(
|
|
493
|
+
strokeWidth: 2,
|
|
494
|
+
color: Colors.white,
|
|
495
|
+
),
|
|
496
|
+
)
|
|
497
|
+
: Icon(Icons.save_outlined),
|
|
498
|
+
label: Text('Save'),
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
295
502
|
Widget _buildSettingsOverview(
|
|
296
503
|
NeoAgentController controller,
|
|
297
504
|
int availableModelCount,
|
|
@@ -634,7 +841,11 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
634
841
|
.map(
|
|
635
842
|
(model) => DropdownMenuItem<String>(
|
|
636
843
|
value: model.id,
|
|
637
|
-
child: Text(
|
|
844
|
+
child: Text(
|
|
845
|
+
model.label,
|
|
846
|
+
maxLines: 1,
|
|
847
|
+
overflow: TextOverflow.ellipsis,
|
|
848
|
+
),
|
|
638
849
|
),
|
|
639
850
|
)
|
|
640
851
|
.toList(),
|
|
@@ -2030,6 +2241,7 @@ class _RoutingSelectCard extends StatelessWidget {
|
|
|
2030
2241
|
DropdownButtonFormField<String>(
|
|
2031
2242
|
initialValue: value,
|
|
2032
2243
|
items: items,
|
|
2244
|
+
isExpanded: true,
|
|
2033
2245
|
decoration: const InputDecoration(isDense: true),
|
|
2034
2246
|
onChanged: onChanged,
|
|
2035
2247
|
),
|
|
@@ -232,13 +232,10 @@ List<AppSection> _mainSections(NeoAgentController controller) {
|
|
|
232
232
|
AppSection.chat,
|
|
233
233
|
AppSection.recordings,
|
|
234
234
|
AppSection.runs,
|
|
235
|
-
AppSection.logs,
|
|
236
235
|
AppSection.devices,
|
|
237
236
|
AppSection.tasks,
|
|
238
237
|
AppSection.widgets,
|
|
239
|
-
AppSection.skills,
|
|
240
238
|
AppSection.integrations,
|
|
241
|
-
AppSection.mcp,
|
|
242
239
|
AppSection.memory,
|
|
243
240
|
if (controller.showHealthSection) AppSection.health,
|
|
244
241
|
AppSection.settings,
|
|
@@ -256,7 +253,7 @@ List<Widget> _buildSidebarItems(
|
|
|
256
253
|
final widgets = <Widget>[];
|
|
257
254
|
final mainSections = _mainSections(controller);
|
|
258
255
|
final selectedSidebarSection = mainSections.contains(
|
|
259
|
-
controller.selectedSection,
|
|
256
|
+
controller.selectedSection.canonicalSection,
|
|
260
257
|
);
|
|
261
258
|
for (final group in SidebarGroup.values) {
|
|
262
259
|
final sections = mainSections
|
|
@@ -267,7 +264,8 @@ List<Widget> _buildSidebarItems(
|
|
|
267
264
|
}
|
|
268
265
|
|
|
269
266
|
final active =
|
|
270
|
-
selectedSidebarSection &&
|
|
267
|
+
selectedSidebarSection &&
|
|
268
|
+
controller.selectedSection.canonicalSection.group == group;
|
|
271
269
|
final defaultSection = sections.first;
|
|
272
270
|
final hasChildren = sections.length > 1;
|
|
273
271
|
final expanded = expandedGroup == group;
|
|
@@ -299,7 +297,7 @@ List<Widget> _buildSidebarItems(
|
|
|
299
297
|
_SidebarButton(
|
|
300
298
|
label: section.label,
|
|
301
299
|
icon: section.icon,
|
|
302
|
-
active: controller.selectedSection == section,
|
|
300
|
+
active: controller.selectedSection.canonicalSection == section,
|
|
303
301
|
indent: 18,
|
|
304
302
|
iconSize: 16,
|
|
305
303
|
fontSize: 12,
|
|
@@ -1826,6 +1824,167 @@ class _GlobalWebUpdateBanner extends StatelessWidget {
|
|
|
1826
1824
|
}
|
|
1827
1825
|
}
|
|
1828
1826
|
|
|
1827
|
+
class _GlobalAnalyticsConsentBanner extends StatelessWidget {
|
|
1828
|
+
const _GlobalAnalyticsConsentBanner({required this.controller});
|
|
1829
|
+
|
|
1830
|
+
final NeoAgentController controller;
|
|
1831
|
+
|
|
1832
|
+
@override
|
|
1833
|
+
Widget build(BuildContext context) {
|
|
1834
|
+
return LayoutBuilder(
|
|
1835
|
+
builder: (context, constraints) {
|
|
1836
|
+
final compact = constraints.maxWidth < 700;
|
|
1837
|
+
final body = Text(
|
|
1838
|
+
'NeoAgent uses anonymous analytics to understand startup, navigation, and setup flows. No messages, credentials, or personal content are collected.',
|
|
1839
|
+
style: TextStyle(color: _textSecondary, height: 1.35),
|
|
1840
|
+
);
|
|
1841
|
+
return Material(
|
|
1842
|
+
color: Colors.transparent,
|
|
1843
|
+
child: _GlassSurface(
|
|
1844
|
+
borderRadius: BorderRadius.circular(22),
|
|
1845
|
+
blurSigma: 26,
|
|
1846
|
+
fillColor: _bgCard.withValues(alpha: 0.94),
|
|
1847
|
+
borderColor: _accent.withValues(alpha: 0.18),
|
|
1848
|
+
overlayGradient: LinearGradient(
|
|
1849
|
+
colors: <Color>[
|
|
1850
|
+
_accent.withValues(alpha: 0.12),
|
|
1851
|
+
_bgCard.withValues(alpha: 0.88),
|
|
1852
|
+
_bgSecondary.withValues(alpha: 0.86),
|
|
1853
|
+
],
|
|
1854
|
+
stops: const <double>[0, 0.22, 1],
|
|
1855
|
+
begin: const Alignment(-1, -1),
|
|
1856
|
+
end: const Alignment(1, 1),
|
|
1857
|
+
),
|
|
1858
|
+
boxShadow: <BoxShadow>[
|
|
1859
|
+
BoxShadow(
|
|
1860
|
+
color: Colors.black.withValues(alpha: 0.22),
|
|
1861
|
+
blurRadius: 34,
|
|
1862
|
+
offset: const Offset(0, 18),
|
|
1863
|
+
),
|
|
1864
|
+
BoxShadow(
|
|
1865
|
+
color: _accent.withValues(alpha: 0.1),
|
|
1866
|
+
blurRadius: 24,
|
|
1867
|
+
offset: const Offset(0, 8),
|
|
1868
|
+
),
|
|
1869
|
+
],
|
|
1870
|
+
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
|
|
1871
|
+
child: compact
|
|
1872
|
+
? Column(
|
|
1873
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1874
|
+
children: <Widget>[
|
|
1875
|
+
Row(
|
|
1876
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1877
|
+
children: <Widget>[
|
|
1878
|
+
Container(
|
|
1879
|
+
padding: const EdgeInsets.all(10),
|
|
1880
|
+
decoration: BoxDecoration(
|
|
1881
|
+
color: _accent.withValues(alpha: 0.16),
|
|
1882
|
+
shape: BoxShape.circle,
|
|
1883
|
+
border: Border.all(
|
|
1884
|
+
color: _accent.withValues(alpha: 0.24),
|
|
1885
|
+
),
|
|
1886
|
+
),
|
|
1887
|
+
child: Icon(
|
|
1888
|
+
Icons.cookie_outlined,
|
|
1889
|
+
color: _accent,
|
|
1890
|
+
size: 22,
|
|
1891
|
+
),
|
|
1892
|
+
),
|
|
1893
|
+
const SizedBox(width: 12),
|
|
1894
|
+
Expanded(
|
|
1895
|
+
child: Column(
|
|
1896
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1897
|
+
children: <Widget>[
|
|
1898
|
+
Text(
|
|
1899
|
+
'Analytics cookies',
|
|
1900
|
+
style: TextStyle(
|
|
1901
|
+
color: _textPrimary,
|
|
1902
|
+
fontSize: 15,
|
|
1903
|
+
fontWeight: FontWeight.w700,
|
|
1904
|
+
),
|
|
1905
|
+
),
|
|
1906
|
+
const SizedBox(height: 6),
|
|
1907
|
+
body,
|
|
1908
|
+
],
|
|
1909
|
+
),
|
|
1910
|
+
),
|
|
1911
|
+
],
|
|
1912
|
+
),
|
|
1913
|
+
const SizedBox(height: 14),
|
|
1914
|
+
Row(
|
|
1915
|
+
children: <Widget>[
|
|
1916
|
+
Expanded(
|
|
1917
|
+
child: OutlinedButton(
|
|
1918
|
+
onPressed: controller.declineAnalyticsConsent,
|
|
1919
|
+
child: const Text('Decline'),
|
|
1920
|
+
),
|
|
1921
|
+
),
|
|
1922
|
+
const SizedBox(width: 10),
|
|
1923
|
+
Expanded(
|
|
1924
|
+
child: FilledButton(
|
|
1925
|
+
onPressed: controller.acceptAnalyticsConsent,
|
|
1926
|
+
child: const Text('Allow analytics'),
|
|
1927
|
+
),
|
|
1928
|
+
),
|
|
1929
|
+
],
|
|
1930
|
+
),
|
|
1931
|
+
],
|
|
1932
|
+
)
|
|
1933
|
+
: Row(
|
|
1934
|
+
crossAxisAlignment: CrossAxisAlignment.center,
|
|
1935
|
+
children: <Widget>[
|
|
1936
|
+
Container(
|
|
1937
|
+
padding: const EdgeInsets.all(10),
|
|
1938
|
+
decoration: BoxDecoration(
|
|
1939
|
+
color: _accent.withValues(alpha: 0.16),
|
|
1940
|
+
shape: BoxShape.circle,
|
|
1941
|
+
border: Border.all(
|
|
1942
|
+
color: _accent.withValues(alpha: 0.24),
|
|
1943
|
+
),
|
|
1944
|
+
),
|
|
1945
|
+
child: Icon(
|
|
1946
|
+
Icons.cookie_outlined,
|
|
1947
|
+
color: _accent,
|
|
1948
|
+
size: 22,
|
|
1949
|
+
),
|
|
1950
|
+
),
|
|
1951
|
+
const SizedBox(width: 14),
|
|
1952
|
+
Expanded(
|
|
1953
|
+
child: Column(
|
|
1954
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1955
|
+
children: <Widget>[
|
|
1956
|
+
Text(
|
|
1957
|
+
'Analytics cookies',
|
|
1958
|
+
style: TextStyle(
|
|
1959
|
+
color: _textPrimary,
|
|
1960
|
+
fontSize: 15,
|
|
1961
|
+
fontWeight: FontWeight.w700,
|
|
1962
|
+
),
|
|
1963
|
+
),
|
|
1964
|
+
const SizedBox(height: 6),
|
|
1965
|
+
body,
|
|
1966
|
+
],
|
|
1967
|
+
),
|
|
1968
|
+
),
|
|
1969
|
+
const SizedBox(width: 16),
|
|
1970
|
+
OutlinedButton(
|
|
1971
|
+
onPressed: controller.declineAnalyticsConsent,
|
|
1972
|
+
child: const Text('Decline'),
|
|
1973
|
+
),
|
|
1974
|
+
const SizedBox(width: 10),
|
|
1975
|
+
FilledButton(
|
|
1976
|
+
onPressed: controller.acceptAnalyticsConsent,
|
|
1977
|
+
child: const Text('Allow analytics'),
|
|
1978
|
+
),
|
|
1979
|
+
],
|
|
1980
|
+
),
|
|
1981
|
+
),
|
|
1982
|
+
);
|
|
1983
|
+
},
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1829
1988
|
class _DesktopCloseDecision {
|
|
1830
1989
|
const _DesktopCloseDecision({
|
|
1831
1990
|
required this.keepRunning,
|