neoagent 3.0.1-beta.4 → 3.0.1-beta.5
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/README.md +11 -0
- package/flutter_app/lib/main.dart +2 -1
- package/flutter_app/lib/main_app_shell.dart +306 -0
- package/flutter_app/lib/main_install.dart +1147 -0
- package/flutter_app/lib/main_navigation.dart +6 -0
- package/package.json +1 -1
- package/server/admin/index.html +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +72908 -71991
- package/server/services/ai/preModelCompaction.js +25 -5
- package/server/services/browser/contentExtractor.js +629 -0
- package/server/services/browser/controller.js +4 -8
package/README.md
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-a855f7?style=flat-square" alt="AGPL-3.0 license"></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="https://github.com/NeoLabs-Systems/NeoAgent/releases/latest"><img alt="Android" src="https://img.shields.io/badge/Android-APK-3ddc84?style=flat-square&logo=android&logoColor=white"></a>
|
|
23
|
+
<a href="https://github.com/NeoLabs-Systems/NeoAgent/releases/latest"><img alt="iOS" src="https://img.shields.io/badge/iOS-coming_soon-lightgrey?style=flat-square&logo=apple&logoColor=white"></a>
|
|
24
|
+
<a href="https://github.com/NeoLabs-Systems/NeoAgent/releases/latest"><img alt="Windows" src="https://img.shields.io/badge/Windows-EXE-0078d4?style=flat-square&logo=windows&logoColor=white"></a>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
21
27
|
<p align="center">
|
|
22
28
|
<a href="https://www.producthunt.com/products/neoagent-2?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-neoagent-2" target="_blank" rel="noopener noreferrer"><img alt="NeoAgent - The next-gen self-hosted AI agent that works beyond chat | Product Hunt" width="250" height="54" src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1171573&theme=light&t=1781447653039"></a>
|
|
23
29
|
</p>
|
|
@@ -37,6 +43,11 @@ npm install -g neoagent
|
|
|
37
43
|
neoagent install
|
|
38
44
|
```
|
|
39
45
|
|
|
46
|
+
<p>
|
|
47
|
+
<a href="https://github.com/NeoLabs-Systems/NeoAgent/releases/latest"><img alt="Download macOS app" src="https://img.shields.io/badge/macOS_app-download-black?style=flat-square&logo=apple&logoColor=white"></a>
|
|
48
|
+
The macOS desktop app includes a graphical installer — open it and use the <strong>Server</strong> tab instead of the CLI.
|
|
49
|
+
</p>
|
|
50
|
+
|
|
40
51
|
Open `http://localhost:3333`, create the first account, and configure a model.
|
|
41
52
|
Local models can run through [Ollama](https://ollama.com/); hosted providers
|
|
42
53
|
can be configured in the application.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'dart:async';
|
|
2
2
|
import 'dart:convert';
|
|
3
|
-
import 'dart:io' show Platform;
|
|
3
|
+
import 'dart:io' show Directory, File, Platform, Process, ProcessSignal;
|
|
4
4
|
import 'dart:math' as math;
|
|
5
5
|
import 'dart:ui' show ImageFilter;
|
|
6
6
|
|
|
@@ -72,6 +72,7 @@ part 'main_model_picker.dart';
|
|
|
72
72
|
part 'main_operations.dart';
|
|
73
73
|
part 'main_admin.dart';
|
|
74
74
|
part 'main_unified.dart';
|
|
75
|
+
part 'main_install.dart';
|
|
75
76
|
|
|
76
77
|
Future<void> main() async {
|
|
77
78
|
await runNeoAgentApp(mode: _appModeFromEnvironment());
|
|
@@ -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,200 @@ class _BackendSetupViewState extends State<BackendSetupView> {
|
|
|
195
202
|
),
|
|
196
203
|
),
|
|
197
204
|
),
|
|
205
|
+
const SizedBox(height: 10),
|
|
206
|
+
Center(
|
|
207
|
+
child: TextButton.icon(
|
|
208
|
+
onPressed: () => setState(() => _localInstall = true),
|
|
209
|
+
icon: const Icon(Icons.download_outlined, size: 16),
|
|
210
|
+
label: const Text('Install NeoAgent on this machine'),
|
|
211
|
+
),
|
|
212
|
+
),
|
|
213
|
+
],
|
|
214
|
+
),
|
|
215
|
+
),
|
|
216
|
+
),
|
|
217
|
+
),
|
|
218
|
+
),
|
|
219
|
+
),
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ─── Local Install Widget ────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
enum _LocalInstallPhase { checking, ready, installing, done, failed }
|
|
230
|
+
|
|
231
|
+
class _LocalInstallWidget extends StatefulWidget {
|
|
232
|
+
const _LocalInstallWidget({required this.controller, required this.onBack});
|
|
233
|
+
|
|
234
|
+
final NeoAgentController controller;
|
|
235
|
+
final VoidCallback onBack;
|
|
236
|
+
|
|
237
|
+
@override
|
|
238
|
+
State<_LocalInstallWidget> createState() => _LocalInstallWidgetState();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
class _LocalInstallWidgetState extends State<_LocalInstallWidget> {
|
|
242
|
+
_LocalInstallPhase _phase = _LocalInstallPhase.checking;
|
|
243
|
+
String? _nodePath;
|
|
244
|
+
String? _installDir;
|
|
245
|
+
final List<String> _log = [];
|
|
246
|
+
final _logScrollCtrl = ScrollController();
|
|
247
|
+
Process? _proc;
|
|
248
|
+
String? _errorMsg;
|
|
249
|
+
|
|
250
|
+
String get _home => Platform.isWindows
|
|
251
|
+
? (Platform.environment['USERPROFILE'] ?? '')
|
|
252
|
+
: (Platform.environment['HOME'] ?? '');
|
|
253
|
+
|
|
254
|
+
@override
|
|
255
|
+
void initState() {
|
|
256
|
+
super.initState();
|
|
257
|
+
_check();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@override
|
|
261
|
+
void dispose() {
|
|
262
|
+
_logScrollCtrl.dispose();
|
|
263
|
+
_proc?.kill(ProcessSignal.sigkill);
|
|
264
|
+
super.dispose();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
Future<void> _check() async {
|
|
268
|
+
if (!mounted) return;
|
|
269
|
+
setState(() => _phase = _LocalInstallPhase.checking);
|
|
270
|
+
|
|
271
|
+
final r = await Process.run(Platform.isWindows ? 'where' : 'which', ['node']);
|
|
272
|
+
if (r.exitCode == 0) {
|
|
273
|
+
_nodePath = (r.stdout as String).trim().split('\n').first.trim();
|
|
274
|
+
} else if (!Platform.isWindows) {
|
|
275
|
+
for (final p in ['/opt/homebrew/bin/node', '/usr/local/bin/node']) {
|
|
276
|
+
if (File(p).existsSync()) {
|
|
277
|
+
_nodePath = p;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
final defaultDir = '$_home/NeoAgent';
|
|
284
|
+
if (File('$defaultDir/bin/neoagent.js').existsSync()) {
|
|
285
|
+
_installDir = defaultDir;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!mounted) return;
|
|
289
|
+
if (_nodePath != null && _installDir != null) {
|
|
290
|
+
setState(() => _phase = _LocalInstallPhase.ready);
|
|
291
|
+
} else {
|
|
292
|
+
setState(() {
|
|
293
|
+
_phase = _LocalInstallPhase.failed;
|
|
294
|
+
_errorMsg = _nodePath == null
|
|
295
|
+
? 'Node.js not found. Install it from nodejs.org then retry.'
|
|
296
|
+
: 'NeoAgent not found at ~/NeoAgent. Run: npm install -g neoagent && neoagent install';
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
bool _ensureEnvFile() {
|
|
302
|
+
final envPath = '$_home/.neoagent/runtime/.env';
|
|
303
|
+
if (!File(envPath).existsSync()) {
|
|
304
|
+
try {
|
|
305
|
+
Directory('$_home/.neoagent/runtime').createSync(recursive: true);
|
|
306
|
+
File(envPath).writeAsStringSync('NODE_ENV=production\nPORT=3333\n');
|
|
307
|
+
} catch (_) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
Future<void> _install() async {
|
|
315
|
+
if (!_ensureEnvFile()) {
|
|
316
|
+
setState(() {
|
|
317
|
+
_phase = _LocalInstallPhase.failed;
|
|
318
|
+
_errorMsg = 'Could not create ~/.neoagent/runtime/.env — check directory permissions.';
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
setState(() {
|
|
323
|
+
_phase = _LocalInstallPhase.installing;
|
|
324
|
+
_log.clear();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
final process = await Process.start(
|
|
328
|
+
_nodePath!,
|
|
329
|
+
['bin/neoagent.js', 'install'],
|
|
330
|
+
workingDirectory: _installDir,
|
|
331
|
+
);
|
|
332
|
+
_proc = process;
|
|
333
|
+
|
|
334
|
+
void onChunk(String data) {
|
|
335
|
+
if (!mounted) return;
|
|
336
|
+
setState(() => _log.add(data));
|
|
337
|
+
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
338
|
+
if (_logScrollCtrl.hasClients) {
|
|
339
|
+
_logScrollCtrl.jumpTo(_logScrollCtrl.position.maxScrollExtent);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
process.stdout.transform(utf8.decoder).listen(onChunk);
|
|
345
|
+
process.stderr.transform(utf8.decoder).listen(onChunk);
|
|
346
|
+
|
|
347
|
+
final exit = await process.exitCode;
|
|
348
|
+
_proc = null;
|
|
349
|
+
if (!mounted) return;
|
|
350
|
+
setState(() {
|
|
351
|
+
_phase = exit == 0 ? _LocalInstallPhase.done : _LocalInstallPhase.failed;
|
|
352
|
+
if (exit != 0) _errorMsg = 'Installation failed (exit $exit). Check the log above.';
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@override
|
|
357
|
+
Widget build(BuildContext context) {
|
|
358
|
+
return _AmbientBackdrop(
|
|
359
|
+
child: Scaffold(
|
|
360
|
+
backgroundColor: Colors.transparent,
|
|
361
|
+
body: SafeArea(
|
|
362
|
+
child: Center(
|
|
363
|
+
child: SingleChildScrollView(
|
|
364
|
+
padding: const EdgeInsets.all(24),
|
|
365
|
+
child: ConstrainedBox(
|
|
366
|
+
constraints: const BoxConstraints(maxWidth: 560),
|
|
367
|
+
child: _EntranceMotion(
|
|
368
|
+
child: _GlassSurface(
|
|
369
|
+
borderRadius: BorderRadius.circular(34),
|
|
370
|
+
blurSigma: 28,
|
|
371
|
+
boxShadow: _softPanelShadow,
|
|
372
|
+
overlayGradient: _panelGradient,
|
|
373
|
+
fillColor: _glassFill,
|
|
374
|
+
child: Padding(
|
|
375
|
+
padding: const EdgeInsets.fromLTRB(34, 28, 34, 30),
|
|
376
|
+
child: Column(
|
|
377
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
378
|
+
children: <Widget>[
|
|
379
|
+
TextButton.icon(
|
|
380
|
+
onPressed: widget.onBack,
|
|
381
|
+
icon: const Icon(Icons.arrow_back, size: 16),
|
|
382
|
+
label: const Text('Back'),
|
|
383
|
+
style: TextButton.styleFrom(
|
|
384
|
+
padding: EdgeInsets.zero,
|
|
385
|
+
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
386
|
+
),
|
|
387
|
+
),
|
|
388
|
+
const SizedBox(height: 16),
|
|
389
|
+
const _BrandLockup(logoSize: 60),
|
|
390
|
+
const SizedBox(height: 22),
|
|
391
|
+
Text('INSTALL LOCALLY', style: _sectionEyebrowStyle()),
|
|
392
|
+
const SizedBox(height: 10),
|
|
393
|
+
Text(
|
|
394
|
+
'Install NeoAgent as a background service',
|
|
395
|
+
style: _displayTitleStyle(28),
|
|
396
|
+
),
|
|
397
|
+
const SizedBox(height: 20),
|
|
398
|
+
_buildContent(),
|
|
198
399
|
],
|
|
199
400
|
),
|
|
200
401
|
),
|
|
@@ -207,6 +408,109 @@ class _BackendSetupViewState extends State<BackendSetupView> {
|
|
|
207
408
|
),
|
|
208
409
|
);
|
|
209
410
|
}
|
|
411
|
+
|
|
412
|
+
Widget _buildContent() {
|
|
413
|
+
switch (_phase) {
|
|
414
|
+
case _LocalInstallPhase.checking:
|
|
415
|
+
return const Center(
|
|
416
|
+
child: Padding(
|
|
417
|
+
padding: EdgeInsets.all(24),
|
|
418
|
+
child: CircularProgressIndicator(),
|
|
419
|
+
),
|
|
420
|
+
);
|
|
421
|
+
case _LocalInstallPhase.ready:
|
|
422
|
+
return Column(
|
|
423
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
424
|
+
children: <Widget>[
|
|
425
|
+
Text(
|
|
426
|
+
'NeoAgent found at $_installDir. Click Install to set up the background service.',
|
|
427
|
+
style: TextStyle(color: _textSecondary, height: 1.55),
|
|
428
|
+
),
|
|
429
|
+
const SizedBox(height: 20),
|
|
430
|
+
SizedBox(
|
|
431
|
+
width: double.infinity,
|
|
432
|
+
child: FilledButton.icon(
|
|
433
|
+
onPressed: _install,
|
|
434
|
+
style: FilledButton.styleFrom(
|
|
435
|
+
backgroundColor: _accent,
|
|
436
|
+
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
437
|
+
),
|
|
438
|
+
icon: const Icon(Icons.download_outlined),
|
|
439
|
+
label: const Text('Install NeoAgent'),
|
|
440
|
+
),
|
|
441
|
+
),
|
|
442
|
+
],
|
|
443
|
+
);
|
|
444
|
+
case _LocalInstallPhase.installing:
|
|
445
|
+
return Column(
|
|
446
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
447
|
+
children: <Widget>[
|
|
448
|
+
Row(
|
|
449
|
+
children: <Widget>[
|
|
450
|
+
const SizedBox(
|
|
451
|
+
width: 18,
|
|
452
|
+
height: 18,
|
|
453
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
454
|
+
),
|
|
455
|
+
const SizedBox(width: 12),
|
|
456
|
+
const Expanded(child: Text('Installing...')),
|
|
457
|
+
TextButton(
|
|
458
|
+
onPressed: () async {
|
|
459
|
+
final proc = _proc;
|
|
460
|
+
proc?.kill(ProcessSignal.sigterm);
|
|
461
|
+
await proc?.exitCode;
|
|
462
|
+
if (mounted) setState(() => _phase = _LocalInstallPhase.ready);
|
|
463
|
+
},
|
|
464
|
+
child: const Text('Cancel'),
|
|
465
|
+
),
|
|
466
|
+
],
|
|
467
|
+
),
|
|
468
|
+
const SizedBox(height: 12),
|
|
469
|
+
_InstallLogCard(lines: _log, scrollController: _logScrollCtrl),
|
|
470
|
+
],
|
|
471
|
+
);
|
|
472
|
+
case _LocalInstallPhase.done:
|
|
473
|
+
return Column(
|
|
474
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
475
|
+
children: <Widget>[
|
|
476
|
+
Row(
|
|
477
|
+
children: <Widget>[
|
|
478
|
+
Icon(Icons.check_circle_outline, color: _success),
|
|
479
|
+
const SizedBox(width: 8),
|
|
480
|
+
const Text('NeoAgent is running.'),
|
|
481
|
+
],
|
|
482
|
+
),
|
|
483
|
+
const SizedBox(height: 20),
|
|
484
|
+
SizedBox(
|
|
485
|
+
width: double.infinity,
|
|
486
|
+
child: FilledButton.icon(
|
|
487
|
+
onPressed: () =>
|
|
488
|
+
widget.controller.saveBackendUrl('http://localhost:3333'),
|
|
489
|
+
style: FilledButton.styleFrom(
|
|
490
|
+
backgroundColor: _accent,
|
|
491
|
+
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
492
|
+
),
|
|
493
|
+
icon: const Icon(Icons.arrow_forward_rounded),
|
|
494
|
+
label: const Text('Connect to local NeoAgent'),
|
|
495
|
+
),
|
|
496
|
+
),
|
|
497
|
+
],
|
|
498
|
+
);
|
|
499
|
+
case _LocalInstallPhase.failed:
|
|
500
|
+
return Column(
|
|
501
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
502
|
+
children: <Widget>[
|
|
503
|
+
_InlineError(message: _errorMsg ?? 'Installation failed.'),
|
|
504
|
+
const SizedBox(height: 16),
|
|
505
|
+
OutlinedButton.icon(
|
|
506
|
+
onPressed: _check,
|
|
507
|
+
icon: const Icon(Icons.refresh),
|
|
508
|
+
label: const Text('Retry'),
|
|
509
|
+
),
|
|
510
|
+
],
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
210
514
|
}
|
|
211
515
|
|
|
212
516
|
class AuthView extends StatefulWidget {
|
|
@@ -2216,6 +2520,8 @@ class _SectionBody extends StatelessWidget {
|
|
|
2216
2520
|
return controller.showHealthSection
|
|
2217
2521
|
? HealthPanel(controller: controller)
|
|
2218
2522
|
: ChatPanel(controller: controller);
|
|
2523
|
+
case AppSection.server:
|
|
2524
|
+
return ServerPanel(controller: controller);
|
|
2219
2525
|
}
|
|
2220
2526
|
}
|
|
2221
2527
|
}
|