neoagent 3.0.1-beta.3 → 3.0.1-beta.31
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 +19 -8
- package/README.md +20 -3
- package/docs/automation.md +37 -0
- package/docs/benchmarking.md +102 -0
- package/docs/billing.md +229 -0
- package/docs/configuration.md +22 -0
- package/docs/getting-started.md +10 -8
- package/docs/index.md +1 -0
- package/docs/operations.md +1 -1
- package/flutter_app/lib/main.dart +5 -1
- package/flutter_app/lib/main_account_settings.dart +138 -0
- package/flutter_app/lib/main_app_shell.dart +331 -0
- package/flutter_app/lib/main_billing.dart +1465 -0
- package/flutter_app/lib/main_chat.dart +1612 -214
- package/flutter_app/lib/main_controller.dart +398 -26
- package/flutter_app/lib/main_devices.dart +293 -207
- package/flutter_app/lib/main_install.dart +1147 -0
- package/flutter_app/lib/main_models.dart +142 -0
- package/flutter_app/lib/main_navigation.dart +25 -1
- package/flutter_app/lib/main_operations.dart +288 -9
- package/flutter_app/lib/main_settings.dart +510 -300
- package/flutter_app/lib/main_shared.dart +2 -0
- package/flutter_app/lib/main_timeline.dart +1257 -0
- package/flutter_app/lib/src/backend_client.dart +149 -19
- package/flutter_app/lib/src/desktop_companion_actions.dart +132 -21
- package/flutter_app/lib/src/desktop_companion_io.dart +65 -1
- package/flutter_app/lib/src/desktop_companion_stub.dart +12 -0
- package/flutter_app/lib/src/desktop_ocr_bridge.dart +2 -0
- package/flutter_app/lib/src/desktop_ocr_bridge_io.dart +125 -0
- package/flutter_app/lib/src/desktop_ocr_bridge_stub.dart +30 -0
- package/flutter_app/lib/src/desktop_passive_history.dart +332 -0
- package/flutter_app/lib/src/recording_bridge_io.dart +80 -72
- package/flutter_app/lib/src/recording_bridge_web.dart +127 -114
- package/flutter_app/lib/src/recording_chunk_queue.dart +149 -0
- package/flutter_app/lib/src/recording_chunk_queue_io.dart +182 -0
- package/flutter_app/lib/src/recording_payloads.dart +9 -0
- package/flutter_app/macos/Runner/AppDelegate.swift +25 -0
- package/flutter_app/windows/runner/flutter_window.cpp +75 -0
- package/landing/index.html +5 -5
- package/lib/manager.js +184 -66
- package/lib/schema_migrations.js +84 -0
- package/package.json +10 -4
- package/server/admin/access.js +12 -7
- package/server/admin/admin.css +78 -0
- package/server/admin/admin.js +441 -23
- package/server/admin/billing.js +415 -0
- package/server/admin/index.html +116 -13
- package/server/admin/users.js +15 -15
- package/server/db/database.js +134 -31
- package/server/db/sessions_db.js +8 -0
- package/server/http/middleware.js +4 -4
- package/server/http/routes.js +10 -0
- package/server/http/static.js +4 -2
- package/server/middleware/requireBilling.js +12 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/canvaskit/wimp.js.symbols +8475 -8467
- package/server/public/canvaskit/wimp.wasm +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +91193 -86282
- package/server/routes/account.js +53 -0
- package/server/routes/admin.js +472 -63
- package/server/routes/agents.js +203 -21
- package/server/routes/billing.js +127 -0
- package/server/routes/billing_webhook.js +43 -0
- package/server/routes/browser.js +8 -1
- package/server/routes/memory.js +1 -4
- package/server/routes/recordings.js +96 -6
- package/server/routes/screenHistory.js +140 -2
- package/server/routes/settings.js +1 -2
- package/server/routes/skills.js +1 -1
- package/server/routes/timeline.js +43 -0
- package/server/routes/triggers.js +2 -2
- package/server/services/account/erasure.js +263 -0
- package/server/services/account/sessions.js +1 -9
- package/server/services/ai/hooks.js +4 -1
- package/server/services/ai/loop/agent_engine_core.js +50 -1
- package/server/services/ai/loop/blank_recovery.js +36 -0
- package/server/services/ai/loop/completion_judge.js +42 -0
- package/server/services/ai/loop/conversation_loop.js +320 -38
- package/server/services/ai/loop/progress_monitor.js +6 -6
- package/server/services/ai/loopPolicy.js +37 -44
- package/server/services/ai/messagingFallback.js +25 -1
- package/server/services/ai/models.js +18 -0
- package/server/services/ai/preModelCompaction.js +25 -5
- package/server/services/ai/rate_limits.js +52 -5
- package/server/services/ai/systemPrompt.js +23 -5
- package/server/services/ai/taskAnalysis.js +2 -0
- package/server/services/ai/toolEvidence.js +15 -0
- package/server/services/ai/toolResult.js +40 -0
- package/server/services/ai/tools.js +237 -22
- package/server/services/android/controller.js +6 -2
- package/server/services/billing/billing_email.js +106 -0
- package/server/services/billing/config.js +17 -0
- package/server/services/billing/plans.js +121 -0
- package/server/services/billing/stripe_client.js +21 -0
- package/server/services/billing/subscriptions.js +462 -0
- package/server/services/billing/trial_guard.js +111 -0
- package/server/services/browser/anti_detection.js +192 -0
- package/server/services/browser/contentExtractor.js +629 -0
- package/server/services/browser/controller.js +181 -59
- package/server/services/desktop/auth.js +3 -0
- package/server/services/desktop/gateway.js +7 -4
- package/server/services/desktop/registry.js +50 -2
- package/server/services/integrations/google/calendar.js +30 -2
- package/server/services/integrations/secrets.js +7 -4
- package/server/services/manager.js +23 -42
- package/server/services/memory/ingestion_chunking.js +268 -0
- package/server/services/messaging/automation.js +1 -1
- package/server/services/messaging/telnyx.js +12 -11
- package/server/services/messaging/whatsapp.js +1 -1
- package/server/services/recordings/manager.js +73 -34
- package/server/services/runtime/backends/local-vm.js +40 -22
- package/server/services/runtime/docker-vm-manager.js +157 -266
- package/server/services/runtime/guest_bootstrap.js +17 -5
- package/server/services/runtime/guest_image.js +188 -0
- package/server/services/runtime/manager.js +0 -1
- package/server/services/runtime/validation.js +3 -8
- package/server/services/social_video/service.js +60 -10
- package/server/services/tasks/runtime.js +313 -19
- package/server/services/tasks/schedule_utils.js +5 -5
- package/server/services/tasks/task_repository.js +13 -0
- package/server/services/timeline/service.js +558 -0
- package/server/services/voice/runtimeManager.js +19 -10
- package/server/services/wearable/gateway.js +8 -5
- package/server/services/websocket.js +26 -8
- package/server/services/workspace/manager.js +37 -3
- package/server/services/desktop/screenRecorder.js +0 -292
- package/server/services/desktop/screen_recorder_support.js +0 -46
|
@@ -161,6 +161,8 @@ class _AccountSettingsPanelState extends State<AccountSettingsPanel> {
|
|
|
161
161
|
String? _emailInlineError;
|
|
162
162
|
String? _passwordSuccessMessage;
|
|
163
163
|
String? _passwordInlineError;
|
|
164
|
+
bool _isExportingData = false;
|
|
165
|
+
bool _isDeletingAccount = false;
|
|
164
166
|
|
|
165
167
|
@override
|
|
166
168
|
void initState() {
|
|
@@ -592,10 +594,146 @@ class _AccountSettingsPanelState extends State<AccountSettingsPanel> {
|
|
|
592
594
|
.toList(),
|
|
593
595
|
),
|
|
594
596
|
],
|
|
597
|
+
const SizedBox(height: 28),
|
|
598
|
+
const _SectionTitle('Your data'),
|
|
599
|
+
const SizedBox(height: 10),
|
|
600
|
+
Text(
|
|
601
|
+
'Download a copy of your data, or permanently delete your account. '
|
|
602
|
+
'Deletion removes all your conversations, memories, files, tasks and '
|
|
603
|
+
'settings and cannot be undone.',
|
|
604
|
+
style: TextStyle(color: _textSecondary, height: 1.45),
|
|
605
|
+
),
|
|
606
|
+
const SizedBox(height: 14),
|
|
607
|
+
Wrap(
|
|
608
|
+
spacing: 12,
|
|
609
|
+
runSpacing: 12,
|
|
610
|
+
children: <Widget>[
|
|
611
|
+
OutlinedButton.icon(
|
|
612
|
+
onPressed: _isExportingData ? null : _exportMyData,
|
|
613
|
+
icon: _isExportingData
|
|
614
|
+
? const SizedBox.square(
|
|
615
|
+
dimension: 16,
|
|
616
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
617
|
+
)
|
|
618
|
+
: const Icon(Icons.download_outlined),
|
|
619
|
+
label: const Text('Export my data'),
|
|
620
|
+
),
|
|
621
|
+
OutlinedButton.icon(
|
|
622
|
+
onPressed: _isDeletingAccount ? null : _confirmDeleteAccount,
|
|
623
|
+
style: OutlinedButton.styleFrom(
|
|
624
|
+
foregroundColor: _danger,
|
|
625
|
+
side: BorderSide(color: _danger.withValues(alpha: 0.6)),
|
|
626
|
+
),
|
|
627
|
+
icon: _isDeletingAccount
|
|
628
|
+
? const SizedBox.square(
|
|
629
|
+
dimension: 16,
|
|
630
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
631
|
+
)
|
|
632
|
+
: const Icon(Icons.delete_forever_outlined),
|
|
633
|
+
label: const Text('Delete account'),
|
|
634
|
+
),
|
|
635
|
+
],
|
|
636
|
+
),
|
|
595
637
|
],
|
|
596
638
|
);
|
|
597
639
|
}
|
|
598
640
|
|
|
641
|
+
Future<void> _exportMyData() async {
|
|
642
|
+
setState(() => _isExportingData = true);
|
|
643
|
+
try {
|
|
644
|
+
final export = await widget.controller.backendClient.exportMyData(
|
|
645
|
+
widget.controller.backendUrl,
|
|
646
|
+
);
|
|
647
|
+
final pretty = const JsonEncoder.withIndent(' ').convert(export);
|
|
648
|
+
await Clipboard.setData(ClipboardData(text: pretty));
|
|
649
|
+
if (!mounted) return;
|
|
650
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
651
|
+
const SnackBar(
|
|
652
|
+
content: Text('Your data export was copied to the clipboard.'),
|
|
653
|
+
),
|
|
654
|
+
);
|
|
655
|
+
} catch (err) {
|
|
656
|
+
if (!mounted) return;
|
|
657
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
658
|
+
SnackBar(content: Text('Could not export your data: $err')),
|
|
659
|
+
);
|
|
660
|
+
} finally {
|
|
661
|
+
if (mounted) setState(() => _isExportingData = false);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
Future<void> _confirmDeleteAccount() async {
|
|
666
|
+
final username = widget.controller.user?['username']?.toString() ?? '';
|
|
667
|
+
final confirmController = TextEditingController();
|
|
668
|
+
final confirmed = await showDialog<bool>(
|
|
669
|
+
context: context,
|
|
670
|
+
builder: (dialogContext) {
|
|
671
|
+
return StatefulBuilder(
|
|
672
|
+
builder: (context, setDialogState) {
|
|
673
|
+
final matches = confirmController.text.trim() == username;
|
|
674
|
+
return AlertDialog(
|
|
675
|
+
title: const Text('Delete account permanently?'),
|
|
676
|
+
content: Column(
|
|
677
|
+
mainAxisSize: MainAxisSize.min,
|
|
678
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
679
|
+
children: <Widget>[
|
|
680
|
+
const Text(
|
|
681
|
+
'This permanently erases all of your data and cannot be '
|
|
682
|
+
'undone. Type your username to confirm.',
|
|
683
|
+
),
|
|
684
|
+
const SizedBox(height: 14),
|
|
685
|
+
TextField(
|
|
686
|
+
controller: confirmController,
|
|
687
|
+
autofocus: true,
|
|
688
|
+
onChanged: (_) => setDialogState(() {}),
|
|
689
|
+
decoration: InputDecoration(
|
|
690
|
+
labelText: 'Username',
|
|
691
|
+
hintText: username,
|
|
692
|
+
),
|
|
693
|
+
),
|
|
694
|
+
],
|
|
695
|
+
),
|
|
696
|
+
actions: <Widget>[
|
|
697
|
+
TextButton(
|
|
698
|
+
onPressed: () => Navigator.of(dialogContext).pop(false),
|
|
699
|
+
child: const Text('Cancel'),
|
|
700
|
+
),
|
|
701
|
+
FilledButton(
|
|
702
|
+
style: FilledButton.styleFrom(backgroundColor: _danger),
|
|
703
|
+
onPressed: matches
|
|
704
|
+
? () => Navigator.of(dialogContext).pop(true)
|
|
705
|
+
: null,
|
|
706
|
+
child: const Text('Delete forever'),
|
|
707
|
+
),
|
|
708
|
+
],
|
|
709
|
+
);
|
|
710
|
+
},
|
|
711
|
+
);
|
|
712
|
+
},
|
|
713
|
+
);
|
|
714
|
+
confirmController.dispose();
|
|
715
|
+
if (confirmed != true) return;
|
|
716
|
+
|
|
717
|
+
setState(() => _isDeletingAccount = true);
|
|
718
|
+
try {
|
|
719
|
+
await widget.controller.backendClient.deleteMyAccount(
|
|
720
|
+
baseUrl: widget.controller.backendUrl,
|
|
721
|
+
confirmUsername: username,
|
|
722
|
+
);
|
|
723
|
+
if (!mounted) return;
|
|
724
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
725
|
+
const SnackBar(content: Text('Your account has been deleted.')),
|
|
726
|
+
);
|
|
727
|
+
await widget.controller.logout();
|
|
728
|
+
} catch (err) {
|
|
729
|
+
if (!mounted) return;
|
|
730
|
+
setState(() => _isDeletingAccount = false);
|
|
731
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
732
|
+
SnackBar(content: Text('Could not delete your account: $err')),
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
599
737
|
String _formatTokens(int amount) {
|
|
600
738
|
if (amount >= 1000000) {
|
|
601
739
|
final value = amount / 1000000;
|
|
@@ -57,6 +57,7 @@ class BackendSetupView extends StatefulWidget {
|
|
|
57
57
|
|
|
58
58
|
class _BackendSetupViewState extends State<BackendSetupView> {
|
|
59
59
|
late final TextEditingController _backendUrlController;
|
|
60
|
+
bool _localInstall = false;
|
|
60
61
|
|
|
61
62
|
@override
|
|
62
63
|
void initState() {
|
|
@@ -78,6 +79,12 @@ class _BackendSetupViewState extends State<BackendSetupView> {
|
|
|
78
79
|
|
|
79
80
|
@override
|
|
80
81
|
Widget build(BuildContext context) {
|
|
82
|
+
if (_localInstall) {
|
|
83
|
+
return _LocalInstallWidget(
|
|
84
|
+
controller: widget.controller,
|
|
85
|
+
onBack: () => setState(() => _localInstall = false),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
81
88
|
final controller = widget.controller;
|
|
82
89
|
return _AmbientBackdrop(
|
|
83
90
|
child: Scaffold(
|
|
@@ -195,6 +202,212 @@ class _BackendSetupViewState extends State<BackendSetupView> {
|
|
|
195
202
|
),
|
|
196
203
|
),
|
|
197
204
|
),
|
|
205
|
+
const SizedBox(height: 10),
|
|
206
|
+
Center(
|
|
207
|
+
child: TextButton.icon(
|
|
208
|
+
onPressed: () =>
|
|
209
|
+
setState(() => _localInstall = true),
|
|
210
|
+
icon: const Icon(
|
|
211
|
+
Icons.download_outlined,
|
|
212
|
+
size: 16,
|
|
213
|
+
),
|
|
214
|
+
label: const Text(
|
|
215
|
+
'Install NeoAgent on this machine',
|
|
216
|
+
),
|
|
217
|
+
),
|
|
218
|
+
),
|
|
219
|
+
],
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
),
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
),
|
|
226
|
+
),
|
|
227
|
+
),
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ─── Local Install Widget ────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
enum _LocalInstallPhase { checking, ready, installing, done, failed }
|
|
236
|
+
|
|
237
|
+
class _LocalInstallWidget extends StatefulWidget {
|
|
238
|
+
const _LocalInstallWidget({required this.controller, required this.onBack});
|
|
239
|
+
|
|
240
|
+
final NeoAgentController controller;
|
|
241
|
+
final VoidCallback onBack;
|
|
242
|
+
|
|
243
|
+
@override
|
|
244
|
+
State<_LocalInstallWidget> createState() => _LocalInstallWidgetState();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
class _LocalInstallWidgetState extends State<_LocalInstallWidget> {
|
|
248
|
+
_LocalInstallPhase _phase = _LocalInstallPhase.checking;
|
|
249
|
+
String? _nodePath;
|
|
250
|
+
String? _installDir;
|
|
251
|
+
final List<String> _log = [];
|
|
252
|
+
final _logScrollCtrl = ScrollController();
|
|
253
|
+
Process? _proc;
|
|
254
|
+
String? _errorMsg;
|
|
255
|
+
|
|
256
|
+
String get _home => Platform.isWindows
|
|
257
|
+
? (Platform.environment['USERPROFILE'] ?? '')
|
|
258
|
+
: (Platform.environment['HOME'] ?? '');
|
|
259
|
+
|
|
260
|
+
@override
|
|
261
|
+
void initState() {
|
|
262
|
+
super.initState();
|
|
263
|
+
_check();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@override
|
|
267
|
+
void dispose() {
|
|
268
|
+
_logScrollCtrl.dispose();
|
|
269
|
+
_proc?.kill(ProcessSignal.sigkill);
|
|
270
|
+
super.dispose();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
Future<void> _check() async {
|
|
274
|
+
if (!mounted) return;
|
|
275
|
+
setState(() => _phase = _LocalInstallPhase.checking);
|
|
276
|
+
|
|
277
|
+
final r = await Process.run(Platform.isWindows ? 'where' : 'which', [
|
|
278
|
+
'node',
|
|
279
|
+
]);
|
|
280
|
+
if (r.exitCode == 0) {
|
|
281
|
+
_nodePath = (r.stdout as String).trim().split('\n').first.trim();
|
|
282
|
+
} else if (!Platform.isWindows) {
|
|
283
|
+
for (final p in ['/opt/homebrew/bin/node', '/usr/local/bin/node']) {
|
|
284
|
+
if (File(p).existsSync()) {
|
|
285
|
+
_nodePath = p;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
final defaultDir = '$_home/NeoAgent';
|
|
292
|
+
if (File('$defaultDir/bin/neoagent.js').existsSync()) {
|
|
293
|
+
_installDir = defaultDir;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!mounted) return;
|
|
297
|
+
if (_nodePath != null && _installDir != null) {
|
|
298
|
+
setState(() => _phase = _LocalInstallPhase.ready);
|
|
299
|
+
} else {
|
|
300
|
+
setState(() {
|
|
301
|
+
_phase = _LocalInstallPhase.failed;
|
|
302
|
+
_errorMsg = _nodePath == null
|
|
303
|
+
? 'Node.js not found. Install it from nodejs.org then retry.'
|
|
304
|
+
: 'NeoAgent not found at ~/NeoAgent. Run: npm install -g neoagent && neoagent install';
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
bool _ensureEnvFile() {
|
|
310
|
+
final envPath = '$_home/.neoagent/runtime/.env';
|
|
311
|
+
if (!File(envPath).existsSync()) {
|
|
312
|
+
try {
|
|
313
|
+
Directory('$_home/.neoagent/runtime').createSync(recursive: true);
|
|
314
|
+
File(envPath).writeAsStringSync('NODE_ENV=production\nPORT=3333\n');
|
|
315
|
+
} catch (_) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
Future<void> _install() async {
|
|
323
|
+
if (!_ensureEnvFile()) {
|
|
324
|
+
setState(() {
|
|
325
|
+
_phase = _LocalInstallPhase.failed;
|
|
326
|
+
_errorMsg =
|
|
327
|
+
'Could not create ~/.neoagent/runtime/.env — check directory permissions.';
|
|
328
|
+
});
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
setState(() {
|
|
332
|
+
_phase = _LocalInstallPhase.installing;
|
|
333
|
+
_log.clear();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
final process = await Process.start(_nodePath!, [
|
|
337
|
+
'bin/neoagent.js',
|
|
338
|
+
'install',
|
|
339
|
+
], workingDirectory: _installDir);
|
|
340
|
+
_proc = process;
|
|
341
|
+
|
|
342
|
+
void onChunk(String data) {
|
|
343
|
+
if (!mounted) return;
|
|
344
|
+
setState(() => _log.add(data));
|
|
345
|
+
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
346
|
+
if (_logScrollCtrl.hasClients) {
|
|
347
|
+
_logScrollCtrl.jumpTo(_logScrollCtrl.position.maxScrollExtent);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
process.stdout.transform(utf8.decoder).listen(onChunk);
|
|
353
|
+
process.stderr.transform(utf8.decoder).listen(onChunk);
|
|
354
|
+
|
|
355
|
+
final exit = await process.exitCode;
|
|
356
|
+
_proc = null;
|
|
357
|
+
if (!mounted) return;
|
|
358
|
+
setState(() {
|
|
359
|
+
_phase = exit == 0 ? _LocalInstallPhase.done : _LocalInstallPhase.failed;
|
|
360
|
+
if (exit != 0)
|
|
361
|
+
_errorMsg = 'Installation failed (exit $exit). Check the log above.';
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
@override
|
|
366
|
+
Widget build(BuildContext context) {
|
|
367
|
+
return _AmbientBackdrop(
|
|
368
|
+
child: Scaffold(
|
|
369
|
+
backgroundColor: Colors.transparent,
|
|
370
|
+
body: SafeArea(
|
|
371
|
+
child: Center(
|
|
372
|
+
child: SingleChildScrollView(
|
|
373
|
+
padding: const EdgeInsets.all(24),
|
|
374
|
+
child: ConstrainedBox(
|
|
375
|
+
constraints: const BoxConstraints(maxWidth: 560),
|
|
376
|
+
child: _EntranceMotion(
|
|
377
|
+
child: _GlassSurface(
|
|
378
|
+
borderRadius: BorderRadius.circular(34),
|
|
379
|
+
blurSigma: 28,
|
|
380
|
+
boxShadow: _softPanelShadow,
|
|
381
|
+
overlayGradient: _panelGradient,
|
|
382
|
+
fillColor: _glassFill,
|
|
383
|
+
child: Padding(
|
|
384
|
+
padding: const EdgeInsets.fromLTRB(34, 28, 34, 30),
|
|
385
|
+
child: Column(
|
|
386
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
387
|
+
children: <Widget>[
|
|
388
|
+
TextButton.icon(
|
|
389
|
+
onPressed: widget.onBack,
|
|
390
|
+
icon: const Icon(Icons.arrow_back, size: 16),
|
|
391
|
+
label: const Text('Back'),
|
|
392
|
+
style: TextButton.styleFrom(
|
|
393
|
+
padding: EdgeInsets.zero,
|
|
394
|
+
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
395
|
+
),
|
|
396
|
+
),
|
|
397
|
+
const SizedBox(height: 16),
|
|
398
|
+
const _BrandLockup(logoSize: 60),
|
|
399
|
+
const SizedBox(height: 22),
|
|
400
|
+
Text(
|
|
401
|
+
'INSTALL LOCALLY',
|
|
402
|
+
style: _sectionEyebrowStyle(),
|
|
403
|
+
),
|
|
404
|
+
const SizedBox(height: 10),
|
|
405
|
+
Text(
|
|
406
|
+
'Install NeoAgent as a background service',
|
|
407
|
+
style: _displayTitleStyle(28),
|
|
408
|
+
),
|
|
409
|
+
const SizedBox(height: 20),
|
|
410
|
+
_buildContent(),
|
|
198
411
|
],
|
|
199
412
|
),
|
|
200
413
|
),
|
|
@@ -207,6 +420,110 @@ class _BackendSetupViewState extends State<BackendSetupView> {
|
|
|
207
420
|
),
|
|
208
421
|
);
|
|
209
422
|
}
|
|
423
|
+
|
|
424
|
+
Widget _buildContent() {
|
|
425
|
+
switch (_phase) {
|
|
426
|
+
case _LocalInstallPhase.checking:
|
|
427
|
+
return const Center(
|
|
428
|
+
child: Padding(
|
|
429
|
+
padding: EdgeInsets.all(24),
|
|
430
|
+
child: CircularProgressIndicator(),
|
|
431
|
+
),
|
|
432
|
+
);
|
|
433
|
+
case _LocalInstallPhase.ready:
|
|
434
|
+
return Column(
|
|
435
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
436
|
+
children: <Widget>[
|
|
437
|
+
Text(
|
|
438
|
+
'NeoAgent found at $_installDir. Click Install to set up the background service.',
|
|
439
|
+
style: TextStyle(color: _textSecondary, height: 1.55),
|
|
440
|
+
),
|
|
441
|
+
const SizedBox(height: 20),
|
|
442
|
+
SizedBox(
|
|
443
|
+
width: double.infinity,
|
|
444
|
+
child: FilledButton.icon(
|
|
445
|
+
onPressed: _install,
|
|
446
|
+
style: FilledButton.styleFrom(
|
|
447
|
+
backgroundColor: _accent,
|
|
448
|
+
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
449
|
+
),
|
|
450
|
+
icon: const Icon(Icons.download_outlined),
|
|
451
|
+
label: const Text('Install NeoAgent'),
|
|
452
|
+
),
|
|
453
|
+
),
|
|
454
|
+
],
|
|
455
|
+
);
|
|
456
|
+
case _LocalInstallPhase.installing:
|
|
457
|
+
return Column(
|
|
458
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
459
|
+
children: <Widget>[
|
|
460
|
+
Row(
|
|
461
|
+
children: <Widget>[
|
|
462
|
+
const SizedBox(
|
|
463
|
+
width: 18,
|
|
464
|
+
height: 18,
|
|
465
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
466
|
+
),
|
|
467
|
+
const SizedBox(width: 12),
|
|
468
|
+
const Expanded(child: Text('Installing...')),
|
|
469
|
+
TextButton(
|
|
470
|
+
onPressed: () async {
|
|
471
|
+
final proc = _proc;
|
|
472
|
+
proc?.kill(ProcessSignal.sigterm);
|
|
473
|
+
await proc?.exitCode;
|
|
474
|
+
if (mounted)
|
|
475
|
+
setState(() => _phase = _LocalInstallPhase.ready);
|
|
476
|
+
},
|
|
477
|
+
child: const Text('Cancel'),
|
|
478
|
+
),
|
|
479
|
+
],
|
|
480
|
+
),
|
|
481
|
+
const SizedBox(height: 12),
|
|
482
|
+
_InstallLogCard(lines: _log, scrollController: _logScrollCtrl),
|
|
483
|
+
],
|
|
484
|
+
);
|
|
485
|
+
case _LocalInstallPhase.done:
|
|
486
|
+
return Column(
|
|
487
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
488
|
+
children: <Widget>[
|
|
489
|
+
Row(
|
|
490
|
+
children: <Widget>[
|
|
491
|
+
Icon(Icons.check_circle_outline, color: _success),
|
|
492
|
+
const SizedBox(width: 8),
|
|
493
|
+
const Text('NeoAgent is running.'),
|
|
494
|
+
],
|
|
495
|
+
),
|
|
496
|
+
const SizedBox(height: 20),
|
|
497
|
+
SizedBox(
|
|
498
|
+
width: double.infinity,
|
|
499
|
+
child: FilledButton.icon(
|
|
500
|
+
onPressed: () =>
|
|
501
|
+
widget.controller.saveBackendUrl('http://localhost:3333'),
|
|
502
|
+
style: FilledButton.styleFrom(
|
|
503
|
+
backgroundColor: _accent,
|
|
504
|
+
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
505
|
+
),
|
|
506
|
+
icon: const Icon(Icons.arrow_forward_rounded),
|
|
507
|
+
label: const Text('Connect to local NeoAgent'),
|
|
508
|
+
),
|
|
509
|
+
),
|
|
510
|
+
],
|
|
511
|
+
);
|
|
512
|
+
case _LocalInstallPhase.failed:
|
|
513
|
+
return Column(
|
|
514
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
515
|
+
children: <Widget>[
|
|
516
|
+
_InlineError(message: _errorMsg ?? 'Installation failed.'),
|
|
517
|
+
const SizedBox(height: 16),
|
|
518
|
+
OutlinedButton.icon(
|
|
519
|
+
onPressed: _check,
|
|
520
|
+
icon: const Icon(Icons.refresh),
|
|
521
|
+
label: const Text('Retry'),
|
|
522
|
+
),
|
|
523
|
+
],
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
210
527
|
}
|
|
211
528
|
|
|
212
529
|
class AuthView extends StatefulWidget {
|
|
@@ -1391,6 +1708,7 @@ class _HomeViewState extends State<HomeView> {
|
|
|
1391
1708
|
.allowMessagingSuggestion(
|
|
1392
1709
|
notice.platform,
|
|
1393
1710
|
suggestion,
|
|
1711
|
+
chatId: notice.chatId,
|
|
1394
1712
|
);
|
|
1395
1713
|
},
|
|
1396
1714
|
icon: Icon(Icons.verified_user_outlined),
|
|
@@ -1412,6 +1730,13 @@ class _HomeViewState extends State<HomeView> {
|
|
|
1412
1730
|
},
|
|
1413
1731
|
child: Text('Open Messaging'),
|
|
1414
1732
|
),
|
|
1733
|
+
TextButton(
|
|
1734
|
+
onPressed: () async {
|
|
1735
|
+
Navigator.of(dialogContext).pop();
|
|
1736
|
+
await widget.controller.ignoreBlockedSender(notice);
|
|
1737
|
+
},
|
|
1738
|
+
child: Text('Ignore'),
|
|
1739
|
+
),
|
|
1415
1740
|
TextButton(
|
|
1416
1741
|
onPressed: () => Navigator.of(dialogContext).pop(),
|
|
1417
1742
|
child: Text('Dismiss'),
|
|
@@ -2184,6 +2509,8 @@ class _SectionBody extends StatelessWidget {
|
|
|
2184
2509
|
switch (controller.selectedSection) {
|
|
2185
2510
|
case AppSection.chat:
|
|
2186
2511
|
return ChatPanel(controller: controller);
|
|
2512
|
+
case AppSection.timeline:
|
|
2513
|
+
return TimelinePanel(controller: controller);
|
|
2187
2514
|
case AppSection.voiceAssistant:
|
|
2188
2515
|
return VoiceAssistantPanel(controller: controller);
|
|
2189
2516
|
case AppSection.devices:
|
|
@@ -2216,6 +2543,10 @@ class _SectionBody extends StatelessWidget {
|
|
|
2216
2543
|
return controller.showHealthSection
|
|
2217
2544
|
? HealthPanel(controller: controller)
|
|
2218
2545
|
: ChatPanel(controller: controller);
|
|
2546
|
+
case AppSection.server:
|
|
2547
|
+
return ServerPanel(controller: controller);
|
|
2548
|
+
case AppSection.billing:
|
|
2549
|
+
return BillingPanel(controller: controller);
|
|
2219
2550
|
}
|
|
2220
2551
|
}
|
|
2221
2552
|
}
|