neoagent 2.3.1-beta.87 → 2.3.1-beta.88

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.
@@ -5,7 +5,8 @@ import 'package:notification_listener_service/notification_listener_service.dart
5
5
  import 'package:notification_listener_service/notification_event.dart';
6
6
 
7
7
  class NotificationInterceptor {
8
- static final NotificationInterceptor _instance = NotificationInterceptor._internal();
8
+ static final NotificationInterceptor _instance =
9
+ NotificationInterceptor._internal();
9
10
  factory NotificationInterceptor() => _instance;
10
11
  NotificationInterceptor._internal();
11
12
 
@@ -13,59 +14,27 @@ class NotificationInterceptor {
13
14
  String _backendUrl = '';
14
15
  String _token = '';
15
16
 
16
- Future<void> initialize(BuildContext context, String backendUrl, String token) async {
17
+ Future<void> initialize(String backendUrl, String token) async {
17
18
  _backendUrl = backendUrl;
18
19
  _token = token;
19
20
 
20
- bool isGranted = await NotificationListenerService.isPermissionGranted();
21
- if (!isGranted) {
22
- bool userAgreed = await _showPermissionRationale(context);
23
- if (userAgreed) {
24
- await NotificationListenerService.requestPermission();
25
- }
26
- }
27
-
28
- isGranted = await NotificationListenerService.isPermissionGranted();
21
+ final isGranted = await NotificationListenerService.isPermissionGranted();
29
22
  if (isGranted && !_isListening) {
30
23
  _startListening();
31
24
  }
32
25
  }
33
26
 
34
- Future<bool> _showPermissionRationale(BuildContext context) async {
35
- return await showDialog<bool>(
36
- context: context,
37
- builder: (context) => AlertDialog(
38
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
39
- title: const Text('Notification Access Needed'),
40
- content: const Text(
41
- 'NeoAgent can read your incoming notifications (like Calendar alarms or urgent messages) and proactively act on them.\n\n'
42
- 'For example, if you receive a reminder, NeoAgent can automatically prepare relevant context for you.',
43
- ),
44
- actions: [
45
- TextButton(
46
- onPressed: () => Navigator.of(context).pop(false),
47
- child: const Text('Not Now'),
48
- ),
49
- ElevatedButton(
50
- style: ElevatedButton.styleFrom(
51
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
52
- ),
53
- onPressed: () => Navigator.of(context).pop(true),
54
- child: const Text('Allow Access'),
55
- ),
56
- ],
57
- ),
58
- ) ?? false;
59
- }
60
-
61
27
  void _startListening() {
62
28
  _isListening = true;
63
- NotificationListenerService.notificationsStream.listen((ServiceNotificationEvent event) {
29
+ NotificationListenerService.notificationsStream.listen((
30
+ ServiceNotificationEvent event,
31
+ ) {
64
32
  // Filter out noisy system notifications or ongoing foreground services
65
- if (event.packageName == null || event.packageName!.contains('android.system')) {
33
+ if (event.packageName == null ||
34
+ event.packageName!.contains('android.system')) {
66
35
  return;
67
36
  }
68
-
37
+
69
38
  // We only want to intercept newly posted notifications, not removed ones
70
39
  if (event.hasRemoved == true) return;
71
40
 
@@ -79,10 +48,7 @@ class NotificationInterceptor {
79
48
  try {
80
49
  await http.post(
81
50
  Uri.parse('$_backendUrl/api/triggers/notification'),
82
- headers: {
83
- 'Content-Type': 'application/json',
84
- 'Cookie': _token,
85
- },
51
+ headers: {'Content-Type': 'application/json', 'Cookie': _token},
86
52
  body: jsonEncode({
87
53
  'app_package': event.packageName ?? 'unknown',
88
54
  'title': event.title ?? '',
@@ -11,6 +11,7 @@ import 'package:flutter/gestures.dart';
11
11
  import 'package:flutter/material.dart';
12
12
  import 'package:flutter/services.dart';
13
13
  import 'package:flutter_markdown/flutter_markdown.dart';
14
+ import 'package:file_picker/file_picker.dart';
14
15
  import 'package:flutter_secure_storage/flutter_secure_storage.dart';
15
16
  import 'package:google_fonts/google_fonts.dart';
16
17
  import 'package:image/image.dart' as img;
@@ -13,17 +13,23 @@ class SplashView extends StatelessWidget {
13
13
  colors: <Color>[_accent, _bgSecondary, _bgPrimary],
14
14
  ),
15
15
  ),
16
- child: const Scaffold(
16
+ child: Scaffold(
17
17
  backgroundColor: Colors.transparent,
18
18
  body: Center(
19
19
  child: Column(
20
20
  mainAxisSize: MainAxisSize.min,
21
21
  children: <Widget>[
22
22
  _BrandLockup(logoSize: 52),
23
- SizedBox(height: 18),
24
- CircularProgressIndicator(),
25
- SizedBox(height: 16),
26
- Text('Loading NeoOS'),
23
+ const SizedBox(height: 18),
24
+ SizedBox(
25
+ width: 180,
26
+ child: ClipRRect(
27
+ borderRadius: BorderRadius.circular(999),
28
+ child: const LinearProgressIndicator(minHeight: 4),
29
+ ),
30
+ ),
31
+ const SizedBox(height: 14),
32
+ const Text('Loading NeoOS'),
27
33
  ],
28
34
  ),
29
35
  ),
@@ -1090,11 +1096,7 @@ class _HomeViewState extends State<HomeView> {
1090
1096
  });
1091
1097
 
1092
1098
  if (Platform.isAndroid) {
1093
- NotificationInterceptor().initialize(
1094
- context,
1095
- backendUrl,
1096
- sessionCookie,
1097
- );
1099
+ NotificationInterceptor().initialize(backendUrl, sessionCookie);
1098
1100
  }
1099
1101
  }
1100
1102
  }
@@ -1242,12 +1244,19 @@ class _HomeViewState extends State<HomeView> {
1242
1244
  onToggleGroup: _toggleSidebarGroup,
1243
1245
  ),
1244
1246
  appBar: AppBar(
1247
+ toolbarHeight: 48,
1248
+ titleSpacing: 0,
1249
+ leadingWidth: 44,
1250
+ centerTitle: false,
1245
1251
  title: Text(controller.selectedSection.navigationTitle),
1252
+ titleTextStyle: Theme.of(
1253
+ context,
1254
+ ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
1246
1255
  elevation: 0,
1247
1256
  ),
1248
1257
  body: SafeArea(
1249
1258
  child: Padding(
1250
- padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
1259
+ padding: const EdgeInsets.fromLTRB(12, 0, 12, 10),
1251
1260
  child: _GlassSurface(
1252
1261
  borderRadius: BorderRadius.circular(26),
1253
1262
  blurSigma: 24,
@@ -2014,7 +2023,7 @@ class _MobileDrawer extends StatelessWidget {
2014
2023
  child: Column(
2015
2024
  children: <Widget>[
2016
2025
  Padding(
2017
- padding: const EdgeInsets.fromLTRB(16, 18, 16, 14),
2026
+ padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
2018
2027
  child: Column(
2019
2028
  crossAxisAlignment: CrossAxisAlignment.start,
2020
2029
  children: <Widget>[
@@ -2032,7 +2041,7 @@ class _MobileDrawer extends StatelessWidget {
2032
2041
  ],
2033
2042
  ),
2034
2043
  if (controller.agentProfiles.isNotEmpty) ...<Widget>[
2035
- const SizedBox(height: 12),
2044
+ const SizedBox(height: 10),
2036
2045
  _AgentSwitcher(
2037
2046
  controller: controller,
2038
2047
  onChanged: () => Navigator.of(context).pop(),
@@ -2056,7 +2065,7 @@ class _MobileDrawer extends StatelessWidget {
2056
2065
  ),
2057
2066
  ),
2058
2067
  Padding(
2059
- padding: const EdgeInsets.all(8),
2068
+ padding: const EdgeInsets.fromLTRB(8, 6, 8, 8),
2060
2069
  child: Row(
2061
2070
  children: <Widget>[
2062
2071
  const Spacer(),
@@ -14,6 +14,7 @@ class _ChatPanelState extends State<ChatPanel> {
14
14
  final ScrollController _scrollController = ScrollController();
15
15
  List<SharedChatAttachment> _pendingSharedAttachments =
16
16
  const <SharedChatAttachment>[];
17
+ String? _appliedSharedPayloadSignature;
17
18
  int _lastMessageCount = 0;
18
19
  int _lastToolCount = 0;
19
20
  String _lastStream = '';
@@ -27,6 +28,17 @@ class _ChatPanelState extends State<ChatPanel> {
27
28
  _consumeQueuedDraft();
28
29
  }
29
30
 
31
+ @override
32
+ void didUpdateWidget(covariant ChatPanel oldWidget) {
33
+ super.didUpdateWidget(oldWidget);
34
+ if (oldWidget.controller != widget.controller) {
35
+ oldWidget.controller.removeListener(_consumeQueuedDraft);
36
+ widget.controller.addListener(_consumeQueuedDraft);
37
+ _appliedSharedPayloadSignature = null;
38
+ _consumeQueuedDraft();
39
+ }
40
+ }
41
+
30
42
  @override
31
43
  void dispose() {
32
44
  widget.controller.removeListener(_consumeQueuedDraft);
@@ -36,26 +48,116 @@ class _ChatPanelState extends State<ChatPanel> {
36
48
  }
37
49
 
38
50
  void _consumeQueuedDraft() {
39
- final draft = widget.controller.takePendingChatDraft();
40
- final attachments = widget.controller.takePendingSharedChatAttachments();
41
- if (draft == null || draft.isEmpty) {
42
- if (attachments.isNotEmpty) {
43
- setState(() {
44
- _pendingSharedAttachments = attachments;
45
- });
51
+ final draft = widget.controller.peekPendingChatDraft();
52
+ final attachments = widget.controller.peekPendingSharedChatAttachments();
53
+ final signature = _sharedPayloadSignature(draft, attachments);
54
+ if (draft == null && attachments.isEmpty) {
55
+ return;
56
+ }
57
+ if (_appliedSharedPayloadSignature == signature) {
58
+ return;
59
+ }
60
+ _appliedSharedPayloadSignature = signature;
61
+ if (!mounted) {
62
+ return;
63
+ }
64
+ setState(() {
65
+ if ((draft ?? '').isNotEmpty && _composerController.text.trim().isEmpty) {
66
+ _composerController
67
+ ..text = draft!
68
+ ..selection = TextSelection.collapsed(offset: draft.length);
46
69
  }
70
+ _pendingSharedAttachments = attachments;
71
+ });
72
+ }
73
+
74
+ String _sharedPayloadSignature(
75
+ String? draft,
76
+ List<SharedChatAttachment> attachments,
77
+ ) {
78
+ final attachmentSignature = attachments
79
+ .map((item) => '${item.uri}|${item.name}|${item.mimeType}')
80
+ .join('::');
81
+ return '${draft ?? ''}::$attachmentSignature';
82
+ }
83
+
84
+ void _clearSharedPayload() {
85
+ widget.controller.clearPendingSharedChatPayload();
86
+ _appliedSharedPayloadSignature = null;
87
+ if (!mounted) {
47
88
  return;
48
89
  }
49
- _composerController
50
- ..text = draft
51
- ..selection = TextSelection.collapsed(offset: draft.length);
52
- if (attachments.isNotEmpty) {
53
- setState(() {
54
- _pendingSharedAttachments = attachments;
55
- });
90
+ setState(() {
91
+ _pendingSharedAttachments = const <SharedChatAttachment>[];
92
+ });
93
+ }
94
+
95
+ String _mimeTypeForFileName(String fileName) {
96
+ final ext = fileName.split('.').last.toLowerCase();
97
+ switch (ext) {
98
+ case 'jpg':
99
+ case 'jpeg':
100
+ return 'image/jpeg';
101
+ case 'png':
102
+ return 'image/png';
103
+ case 'webp':
104
+ return 'image/webp';
105
+ case 'gif':
106
+ return 'image/gif';
107
+ case 'mp4':
108
+ return 'video/mp4';
109
+ case 'mov':
110
+ return 'video/quicktime';
111
+ case 'm4v':
112
+ return 'video/x-m4v';
113
+ case 'mp3':
114
+ return 'audio/mpeg';
115
+ case 'm4a':
116
+ return 'audio/mp4';
117
+ case 'wav':
118
+ return 'audio/wav';
119
+ case 'pdf':
120
+ return 'application/pdf';
121
+ case 'txt':
122
+ return 'text/plain';
123
+ default:
124
+ return 'application/octet-stream';
56
125
  }
57
126
  }
58
127
 
128
+ Future<void> _attachFiles() async {
129
+ final result = await FilePicker.platform.pickFiles(
130
+ allowMultiple: true,
131
+ withData: false,
132
+ type: FileType.any,
133
+ );
134
+ if (!mounted || result == null || result.files.isEmpty) {
135
+ return;
136
+ }
137
+ final attachments = result.files
138
+ .where((file) => file.path?.trim().isNotEmpty == true)
139
+ .map(
140
+ (file) => SharedChatAttachment(
141
+ uri: file.path!,
142
+ name: file.name,
143
+ mimeType: _mimeTypeForFileName(file.name),
144
+ sizeBytes: file.size,
145
+ source: 'file_picker',
146
+ ),
147
+ )
148
+ .where((item) => item.isValid)
149
+ .toList(growable: false);
150
+ if (attachments.isEmpty) {
151
+ return;
152
+ }
153
+ setState(() {
154
+ _pendingSharedAttachments = <SharedChatAttachment>[
155
+ ..._pendingSharedAttachments,
156
+ ...attachments,
157
+ ];
158
+ });
159
+ }
160
+
59
161
  void _scrollToBottom() {
60
162
  WidgetsBinding.instance.addPostFrameCallback((_) {
61
163
  if (!mounted || !_scrollController.hasClients) {
@@ -178,12 +280,12 @@ class _ChatPanelState extends State<ChatPanel> {
178
280
  .map((entry) => entry.value)
179
281
  .toList(growable: false);
180
282
  });
283
+ if (_pendingSharedAttachments.isEmpty) {
284
+ _clearSharedPayload();
285
+ }
181
286
  },
182
287
  onClear: () {
183
- setState(() {
184
- _pendingSharedAttachments =
185
- const <SharedChatAttachment>[];
186
- });
288
+ _clearSharedPayload();
187
289
  },
188
290
  ),
189
291
  ),
@@ -215,6 +317,13 @@ class _ChatPanelState extends State<ChatPanel> {
215
317
  ),
216
318
  ),
217
319
  const SizedBox(width: 8),
320
+ IconButton(
321
+ tooltip: 'Attach files',
322
+ onPressed: _attachFiles,
323
+ icon: const Icon(Icons.attach_file_rounded),
324
+ color: _textSecondary,
325
+ ),
326
+ const SizedBox(width: 2),
218
327
  FilledButton(
219
328
  onPressed: () => controller.setSelectedSection(
220
329
  AppSection.voiceAssistant,
@@ -246,10 +355,7 @@ class _ChatPanelState extends State<ChatPanel> {
246
355
  _composerController.clear();
247
356
  final outgoingAttachments =
248
357
  _pendingSharedAttachments;
249
- setState(() {
250
- _pendingSharedAttachments =
251
- const <SharedChatAttachment>[];
252
- });
358
+ _clearSharedPayload();
253
359
  try {
254
360
  await controller.sendMessage(
255
361
  task,
@@ -2860,6 +2860,7 @@ class NeoAgentController extends ChangeNotifier {
2860
2860
  () => _backendClient.launchBrowser(backendUrl),
2861
2861
  browser: true,
2862
2862
  );
2863
+ browserScreenshotPath = null;
2863
2864
  }
2864
2865
 
2865
2866
  Future<void> navigateBrowserRuntime({
@@ -2971,6 +2972,8 @@ class NeoAgentController extends ChangeNotifier {
2971
2972
  () => _backendClient.closeBrowser(backendUrl),
2972
2973
  browser: true,
2973
2974
  );
2975
+ browserScreenshotPath = null;
2976
+ notifyListeners();
2974
2977
  }
2975
2978
 
2976
2979
  Future<void> startAndroidRuntime() async {
@@ -5695,21 +5698,29 @@ class NeoAgentController extends ChangeNotifier {
5695
5698
  setSelectedSection(AppSection.chat);
5696
5699
  }
5697
5700
 
5701
+ bool get hasPendingSharedChatPayload =>
5702
+ (_pendingChatDraft?.trim().isNotEmpty ?? false) ||
5703
+ _pendingSharedChatAttachments.isNotEmpty;
5704
+
5698
5705
  bool get _isMobilePlatform =>
5699
5706
  !kIsWeb &&
5700
5707
  (defaultTargetPlatform == TargetPlatform.android ||
5701
5708
  defaultTargetPlatform == TargetPlatform.iOS);
5702
5709
 
5703
- String? takePendingChatDraft() {
5704
- final draft = _pendingChatDraft;
5705
- _pendingChatDraft = null;
5706
- return draft;
5710
+ String? peekPendingChatDraft() {
5711
+ final draft = _pendingChatDraft?.trim() ?? '';
5712
+ return draft.isEmpty ? null : draft;
5707
5713
  }
5708
5714
 
5709
- List<SharedChatAttachment> takePendingSharedChatAttachments() {
5710
- final pending = _pendingSharedChatAttachments;
5715
+ List<SharedChatAttachment> peekPendingSharedChatAttachments() {
5716
+ return List<SharedChatAttachment>.unmodifiable(
5717
+ _pendingSharedChatAttachments,
5718
+ );
5719
+ }
5720
+
5721
+ void clearPendingSharedChatPayload() {
5722
+ _pendingChatDraft = null;
5711
5723
  _pendingSharedChatAttachments = const <SharedChatAttachment>[];
5712
- return pending;
5713
5724
  }
5714
5725
 
5715
5726
  String _taskWithSharedAttachments(
@@ -495,6 +495,20 @@ class _SettingsPanelState extends State<SettingsPanel> {
495
495
  );
496
496
  }
497
497
 
498
+ Widget _inlineProgressIndicator() {
499
+ return SizedBox(
500
+ width: 28,
501
+ child: ClipRRect(
502
+ borderRadius: BorderRadius.circular(999),
503
+ child: LinearProgressIndicator(
504
+ minHeight: 3,
505
+ backgroundColor: Colors.white.withValues(alpha: 0.28),
506
+ color: Colors.white,
507
+ ),
508
+ ),
509
+ );
510
+ }
511
+
498
512
  Widget _buildSettingsOverview(
499
513
  NeoAgentController controller,
500
514
  int availableModelCount,
@@ -1421,13 +1435,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
1421
1435
  : () => controller.checkForAppUpdates(),
1422
1436
  style: FilledButton.styleFrom(backgroundColor: _accent),
1423
1437
  icon: controller.isCheckingAppUpdate
1424
- ? const SizedBox.square(
1425
- dimension: 16,
1426
- child: CircularProgressIndicator(
1427
- strokeWidth: 2,
1428
- color: Colors.white,
1429
- ),
1430
- )
1438
+ ? _inlineProgressIndicator()
1431
1439
  : const Icon(Icons.sync),
1432
1440
  label: Text(
1433
1441
  controller.isCheckingAppUpdate
@@ -1605,13 +1613,7 @@ class _SettingsPanelState extends State<SettingsPanel> {
1605
1613
  backgroundColor: _accent,
1606
1614
  ),
1607
1615
  icon: controller.isOpeningAppUpdate
1608
- ? const SizedBox.square(
1609
- dimension: 16,
1610
- child: CircularProgressIndicator(
1611
- strokeWidth: 2,
1612
- color: Colors.white,
1613
- ),
1614
- )
1616
+ ? _inlineProgressIndicator()
1615
1617
  : const Icon(Icons.system_update_alt),
1616
1618
  label: Text(
1617
1619
  controller.isOpeningAppUpdate
@@ -384,26 +384,30 @@ class _PageTitle extends StatelessWidget {
384
384
  @override
385
385
  Widget build(BuildContext context) {
386
386
  final compact = MediaQuery.sizeOf(context).width < 760;
387
+ final titleStyle = compact
388
+ ? _displayTitleStyle(26)
389
+ : _displayTitleStyle(32);
390
+ final subtitleStyle = TextStyle(
391
+ color: _textSecondary,
392
+ height: compact ? 1.38 : 1.5,
393
+ );
387
394
  return _EntranceMotion(
388
395
  child: Padding(
389
- padding: const EdgeInsets.only(bottom: 24),
396
+ padding: EdgeInsets.only(bottom: compact ? 16 : 24),
390
397
  child: compact
391
398
  ? Column(
392
399
  crossAxisAlignment: CrossAxisAlignment.start,
393
400
  children: <Widget>[
394
401
  Text('CONTROL SURFACE', style: _sectionEyebrowStyle()),
402
+ const SizedBox(height: 6),
403
+ Text(title, style: titleStyle),
395
404
  const SizedBox(height: 8),
396
- Text(title, style: _displayTitleStyle(30)),
397
- const SizedBox(height: 10),
398
405
  ConstrainedBox(
399
406
  constraints: const BoxConstraints(maxWidth: 720),
400
- child: Text(
401
- subtitle,
402
- style: TextStyle(color: _textSecondary, height: 1.5),
403
- ),
407
+ child: Text(subtitle, style: subtitleStyle),
404
408
  ),
405
409
  if (trailing != null) ...<Widget>[
406
- const SizedBox(height: 18),
410
+ const SizedBox(height: 12),
407
411
  trailing!,
408
412
  ],
409
413
  ],
@@ -417,17 +421,11 @@ class _PageTitle extends StatelessWidget {
417
421
  children: <Widget>[
418
422
  Text('CONTROL SURFACE', style: _sectionEyebrowStyle()),
419
423
  const SizedBox(height: 8),
420
- Text(title, style: _displayTitleStyle(32)),
424
+ Text(title, style: titleStyle),
421
425
  const SizedBox(height: 10),
422
426
  ConstrainedBox(
423
427
  constraints: const BoxConstraints(maxWidth: 760),
424
- child: Text(
425
- subtitle,
426
- style: TextStyle(
427
- color: _textSecondary,
428
- height: 1.5,
429
- ),
430
- ),
428
+ child: Text(subtitle, style: subtitleStyle),
431
429
  ),
432
430
  ],
433
431
  ),
@@ -1881,6 +1879,13 @@ class _GlobalWebUpdateBanner extends StatelessWidget {
1881
1879
  monitor.isReloading ? 'Reloading...' : 'Reload now',
1882
1880
  ),
1883
1881
  ),
1882
+ if (monitor.isReloading) ...<Widget>[
1883
+ const SizedBox(height: 8),
1884
+ ClipRRect(
1885
+ borderRadius: BorderRadius.circular(999),
1886
+ child: const LinearProgressIndicator(minHeight: 3),
1887
+ ),
1888
+ ],
1884
1889
  ],
1885
1890
  )
1886
1891
  : Row(
@@ -8,6 +8,7 @@ import Foundation
8
8
  import audioplayers_darwin
9
9
  import connectivity_plus
10
10
  import desktop_audio_capture
11
+ import file_picker
11
12
  import flutter_secure_storage_macos
12
13
  import geolocator_apple
13
14
  import hotkey_manager_macos
@@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
28
29
  AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
29
30
  ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
30
31
  AudioCapturePlugin.register(with: registry.registrar(forPlugin: "AudioCapturePlugin"))
32
+ FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
31
33
  FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
32
34
  GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
33
35
  HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
@@ -137,6 +137,14 @@ packages:
137
137
  url: "https://pub.dev"
138
138
  source: hosted
139
139
  version: "2.1.0"
140
+ cross_file:
141
+ dependency: transitive
142
+ description:
143
+ name: cross_file
144
+ sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
145
+ url: "https://pub.dev"
146
+ source: hosted
147
+ version: "0.3.5+2"
140
148
  crypto:
141
149
  dependency: transitive
142
150
  description:
@@ -200,6 +208,14 @@ packages:
200
208
  url: "https://pub.dev"
201
209
  source: hosted
202
210
  version: "7.0.1"
211
+ file_picker:
212
+ dependency: "direct main"
213
+ description:
214
+ name: file_picker
215
+ sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
216
+ url: "https://pub.dev"
217
+ source: hosted
218
+ version: "8.3.7"
203
219
  fixnum:
204
220
  dependency: transitive
205
221
  description:
@@ -269,6 +285,14 @@ packages:
269
285
  url: "https://pub.dev"
270
286
  source: hosted
271
287
  version: "0.7.7+1"
288
+ flutter_plugin_android_lifecycle:
289
+ dependency: transitive
290
+ description:
291
+ name: flutter_plugin_android_lifecycle
292
+ sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
293
+ url: "https://pub.dev"
294
+ source: hosted
295
+ version: "2.0.34"
272
296
  flutter_secure_storage:
273
297
  dependency: "direct main"
274
298
  description:
@@ -11,6 +11,7 @@ dependencies:
11
11
  sdk: flutter
12
12
  cupertino_icons: ^1.0.8
13
13
  flutter_markdown: ^0.7.4+1
14
+ file_picker: ^8.0.7
14
15
  google_fonts: ^6.3.1
15
16
  image: ^4.5.4
16
17
  http: ^1.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.87",
3
+ "version": "2.3.1-beta.88",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",