neoagent 2.3.1-beta.86 → 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.
- package/docs/capabilities.md +2 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +14 -0
- package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +84 -0
- package/flutter_app/lib/features/notifications/notification_interceptor.dart +11 -45
- package/flutter_app/lib/main.dart +1 -0
- package/flutter_app/lib/main_app_shell.dart +23 -14
- package/flutter_app/lib/main_chat.dart +267 -7
- package/flutter_app/lib/main_controller.dart +151 -13
- package/flutter_app/lib/main_models.dart +69 -0
- package/flutter_app/lib/main_operations.dart +248 -0
- package/flutter_app/lib/main_runtime.dart +11 -2
- package/flutter_app/lib/main_settings.dart +182 -183
- package/flutter_app/lib/main_shared.dart +99 -16
- package/flutter_app/lib/src/app_launch_bridge.dart +39 -10
- package/flutter_app/lib/src/backend_client.dart +28 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +2 -0
- package/flutter_app/pubspec.lock +24 -0
- package/flutter_app/pubspec.yaml +1 -0
- package/package.json +1 -1
- package/server/db/database.js +42 -4
- package/server/guest_agent.js +8 -1
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +70 -44
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +70947 -70038
- package/server/routes/browser.js +1 -1
- package/server/routes/memory.js +90 -0
- package/server/routes/social_video.js +66 -0
- package/server/services/ai/systemPrompt.js +1 -0
- package/server/services/ai/toolResult.js +20 -0
- package/server/services/ai/tools.js +30 -0
- package/server/services/android/android_bootstrap_worker.js +1 -0
- package/server/services/android/controller.js +244 -76
- package/server/services/browser/controller.js +24 -8
- package/server/services/manager.js +15 -0
- package/server/services/memory/llm_transfer.js +217 -0
- package/server/services/runtime/backends/local-vm.js +29 -5
- package/server/services/social_video/adapters/base.js +26 -0
- package/server/services/social_video/adapters/index.js +27 -0
- package/server/services/social_video/adapters/instagram.js +17 -0
- package/server/services/social_video/adapters/tiktok.js +17 -0
- package/server/services/social_video/adapters/x.js +17 -0
- package/server/services/social_video/adapters/youtube.js +17 -0
- package/server/services/social_video/captions.js +187 -0
- package/server/services/social_video/frame.js +42 -0
- package/server/services/social_video/index.js +7 -0
- package/server/services/social_video/metadata.js +63 -0
- package/server/services/social_video/result.js +63 -0
- package/server/services/social_video/service.js +650 -0
- package/server/services/social_video/url.js +83 -0
|
@@ -186,6 +186,8 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
186
186
|
<String, RunDetailSnapshot>{};
|
|
187
187
|
String? _selectedWidgetId;
|
|
188
188
|
String? _pendingChatDraft;
|
|
189
|
+
List<SharedChatAttachment> _pendingSharedChatAttachments =
|
|
190
|
+
const <SharedChatAttachment>[];
|
|
189
191
|
|
|
190
192
|
ActiveRunState? activeRun;
|
|
191
193
|
List<ToolEventItem> toolEvents = const <ToolEventItem>[];
|
|
@@ -997,7 +999,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
997
999
|
unawaited(_analytics.trackAppUpdateCheck(silent: silent));
|
|
998
1000
|
if (!appUpdaterConfigured) {
|
|
999
1001
|
appUpdateErrorMessage = kIsWeb
|
|
1000
|
-
?
|
|
1002
|
+
? null
|
|
1001
1003
|
: 'App updates are not configured for this build.';
|
|
1002
1004
|
if (!silent) {
|
|
1003
1005
|
notifyListeners();
|
|
@@ -2443,6 +2445,31 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2443
2445
|
);
|
|
2444
2446
|
notifyListeners();
|
|
2445
2447
|
} catch (_) {}
|
|
2448
|
+
|
|
2449
|
+
Future<String> fetchMemoryTransferPrompt() async {
|
|
2450
|
+
final response = await _backendClient.fetchMemoryTransferPrompt(
|
|
2451
|
+
backendUrl,
|
|
2452
|
+
agentId: _scopedAgentId,
|
|
2453
|
+
);
|
|
2454
|
+
return response['prompt']?.toString() ?? '';
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
Future<MemoryTransferImportResult> importMemoryTransfer(
|
|
2458
|
+
String text, {
|
|
2459
|
+
bool applyBehaviorNotes = true,
|
|
2460
|
+
bool applyCoreMemory = true,
|
|
2461
|
+
}) async {
|
|
2462
|
+
final response = await _backendClient.importMemoryTransfer(
|
|
2463
|
+
backendUrl,
|
|
2464
|
+
text: text,
|
|
2465
|
+
applyBehaviorNotes: applyBehaviorNotes,
|
|
2466
|
+
applyCoreMemory: applyCoreMemory,
|
|
2467
|
+
agentId: _scopedAgentId,
|
|
2468
|
+
);
|
|
2469
|
+
final result = MemoryTransferImportResult.fromJson(response);
|
|
2470
|
+
await refreshMemory();
|
|
2471
|
+
return result;
|
|
2472
|
+
}
|
|
2446
2473
|
}
|
|
2447
2474
|
|
|
2448
2475
|
Future<void> refreshMessaging() async {
|
|
@@ -2550,6 +2577,31 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2550
2577
|
notifyListeners();
|
|
2551
2578
|
}
|
|
2552
2579
|
|
|
2580
|
+
Future<String> fetchMemoryTransferPrompt() async {
|
|
2581
|
+
final response = await _backendClient.fetchMemoryTransferPrompt(
|
|
2582
|
+
backendUrl,
|
|
2583
|
+
agentId: _scopedAgentId,
|
|
2584
|
+
);
|
|
2585
|
+
return response['prompt']?.toString() ?? '';
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
Future<MemoryTransferImportResult> importMemoryTransfer(
|
|
2589
|
+
String text, {
|
|
2590
|
+
bool applyBehaviorNotes = true,
|
|
2591
|
+
bool applyCoreMemory = true,
|
|
2592
|
+
}) async {
|
|
2593
|
+
final response = await _backendClient.importMemoryTransfer(
|
|
2594
|
+
backendUrl,
|
|
2595
|
+
text: text,
|
|
2596
|
+
applyBehaviorNotes: applyBehaviorNotes,
|
|
2597
|
+
applyCoreMemory: applyCoreMemory,
|
|
2598
|
+
agentId: _scopedAgentId,
|
|
2599
|
+
);
|
|
2600
|
+
final result = MemoryTransferImportResult.fromJson(response);
|
|
2601
|
+
await refreshMemory();
|
|
2602
|
+
return result;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2553
2605
|
Future<void> refreshTasks() async {
|
|
2554
2606
|
taskItems = _decodeModelList(
|
|
2555
2607
|
'tasks',
|
|
@@ -2808,6 +2860,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2808
2860
|
() => _backendClient.launchBrowser(backendUrl),
|
|
2809
2861
|
browser: true,
|
|
2810
2862
|
);
|
|
2863
|
+
browserScreenshotPath = null;
|
|
2811
2864
|
}
|
|
2812
2865
|
|
|
2813
2866
|
Future<void> navigateBrowserRuntime({
|
|
@@ -2919,6 +2972,8 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2919
2972
|
() => _backendClient.closeBrowser(backendUrl),
|
|
2920
2973
|
browser: true,
|
|
2921
2974
|
);
|
|
2975
|
+
browserScreenshotPath = null;
|
|
2976
|
+
notifyListeners();
|
|
2922
2977
|
}
|
|
2923
2978
|
|
|
2924
2979
|
Future<void> startAndroidRuntime() async {
|
|
@@ -4408,10 +4463,21 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4408
4463
|
}
|
|
4409
4464
|
}
|
|
4410
4465
|
|
|
4411
|
-
Future<void> sendMessage(
|
|
4466
|
+
Future<void> sendMessage(
|
|
4467
|
+
String task, {
|
|
4468
|
+
List<SharedChatAttachment> sharedAttachments =
|
|
4469
|
+
const <SharedChatAttachment>[],
|
|
4470
|
+
}) async {
|
|
4412
4471
|
final trimmed = task.trim();
|
|
4472
|
+
final normalizedAttachments = sharedAttachments
|
|
4473
|
+
.where((item) => item.isValid)
|
|
4474
|
+
.toList(growable: false);
|
|
4475
|
+
final outgoingTask = _taskWithSharedAttachments(
|
|
4476
|
+
trimmed,
|
|
4477
|
+
normalizedAttachments,
|
|
4478
|
+
);
|
|
4413
4479
|
final canSteerLiveRun = hasLiveRun && _socket != null && socketConnected;
|
|
4414
|
-
if (
|
|
4480
|
+
if (outgoingTask.isEmpty || (isSendingMessage && !canSteerLiveRun)) {
|
|
4415
4481
|
return;
|
|
4416
4482
|
}
|
|
4417
4483
|
unawaited(
|
|
@@ -4424,9 +4490,20 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4424
4490
|
final optimistic = ChatEntry(
|
|
4425
4491
|
id: '',
|
|
4426
4492
|
role: 'user',
|
|
4427
|
-
content: trimmed
|
|
4493
|
+
content: trimmed.isNotEmpty
|
|
4494
|
+
? trimmed
|
|
4495
|
+
: (normalizedAttachments.isNotEmpty
|
|
4496
|
+
? 'Sent shared attachments from mobile app.'
|
|
4497
|
+
: outgoingTask),
|
|
4428
4498
|
platform: 'flutter',
|
|
4429
4499
|
createdAt: DateTime.now(),
|
|
4500
|
+
metadata: normalizedAttachments.isEmpty
|
|
4501
|
+
? const <String, dynamic>{}
|
|
4502
|
+
: <String, dynamic>{
|
|
4503
|
+
'sharedAttachments': normalizedAttachments
|
|
4504
|
+
.map((item) => item.toJson())
|
|
4505
|
+
.toList(growable: false),
|
|
4506
|
+
},
|
|
4430
4507
|
);
|
|
4431
4508
|
chatMessages = <ChatEntry>[...chatMessages, optimistic];
|
|
4432
4509
|
errorMessage = null;
|
|
@@ -4434,14 +4511,14 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4434
4511
|
isSendingMessage = true;
|
|
4435
4512
|
toolEvents = const <ToolEventItem>[];
|
|
4436
4513
|
streamingAssistant = '';
|
|
4437
|
-
activeRun = ActiveRunState.pending(
|
|
4514
|
+
activeRun = ActiveRunState.pending(outgoingTask);
|
|
4438
4515
|
}
|
|
4439
4516
|
notifyListeners();
|
|
4440
4517
|
|
|
4441
4518
|
try {
|
|
4442
4519
|
if (_socket != null && socketConnected) {
|
|
4443
4520
|
_socket!.emit('agent:run', <String, dynamic>{
|
|
4444
|
-
'task':
|
|
4521
|
+
'task': outgoingTask,
|
|
4445
4522
|
'agentId': _scopedAgentId,
|
|
4446
4523
|
'options': <String, dynamic>{'agentId': _scopedAgentId},
|
|
4447
4524
|
});
|
|
@@ -4450,7 +4527,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4450
4527
|
|
|
4451
4528
|
final response = await _backendClient.runTask(
|
|
4452
4529
|
backendUrl,
|
|
4453
|
-
|
|
4530
|
+
outgoingTask,
|
|
4454
4531
|
agentId: _scopedAgentId,
|
|
4455
4532
|
);
|
|
4456
4533
|
final content = response['content']?.toString().trim();
|
|
@@ -4644,9 +4721,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4644
4721
|
}
|
|
4645
4722
|
}
|
|
4646
4723
|
|
|
4647
|
-
Future<bool> updateAccountDisplayName({
|
|
4648
|
-
required String displayName,
|
|
4649
|
-
}) async {
|
|
4724
|
+
Future<bool> updateAccountDisplayName({required String displayName}) async {
|
|
4650
4725
|
isSavingAccountSettings = true;
|
|
4651
4726
|
errorMessage = null;
|
|
4652
4727
|
notifyListeners();
|
|
@@ -5594,6 +5669,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
5594
5669
|
return;
|
|
5595
5670
|
}
|
|
5596
5671
|
_pendingChatDraft = normalized;
|
|
5672
|
+
_pendingSharedChatAttachments = const <SharedChatAttachment>[];
|
|
5597
5673
|
if (!_isMobilePlatform) {
|
|
5598
5674
|
setSelectedSection(AppSection.chat);
|
|
5599
5675
|
} else {
|
|
@@ -5601,15 +5677,77 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
5601
5677
|
}
|
|
5602
5678
|
}
|
|
5603
5679
|
|
|
5680
|
+
void queueSharedChatPayload({
|
|
5681
|
+
String? text,
|
|
5682
|
+
String? subject,
|
|
5683
|
+
List<Map<String, dynamic>> files = const <Map<String, dynamic>>[],
|
|
5684
|
+
}) {
|
|
5685
|
+
final attachments = files
|
|
5686
|
+
.map(SharedChatAttachment.fromJson)
|
|
5687
|
+
.where((item) => item.isValid)
|
|
5688
|
+
.toList(growable: false);
|
|
5689
|
+
final textPart = (text ?? '').toString().trim();
|
|
5690
|
+
final subjectPart = (subject ?? '').toString().trim();
|
|
5691
|
+
final combined = <String>[
|
|
5692
|
+
subjectPart,
|
|
5693
|
+
textPart,
|
|
5694
|
+
].where((part) => part.isNotEmpty).join('\n').trim();
|
|
5695
|
+
|
|
5696
|
+
_pendingChatDraft = combined;
|
|
5697
|
+
_pendingSharedChatAttachments = attachments;
|
|
5698
|
+
setSelectedSection(AppSection.chat);
|
|
5699
|
+
}
|
|
5700
|
+
|
|
5701
|
+
bool get hasPendingSharedChatPayload =>
|
|
5702
|
+
(_pendingChatDraft?.trim().isNotEmpty ?? false) ||
|
|
5703
|
+
_pendingSharedChatAttachments.isNotEmpty;
|
|
5704
|
+
|
|
5604
5705
|
bool get _isMobilePlatform =>
|
|
5605
5706
|
!kIsWeb &&
|
|
5606
5707
|
(defaultTargetPlatform == TargetPlatform.android ||
|
|
5607
5708
|
defaultTargetPlatform == TargetPlatform.iOS);
|
|
5608
5709
|
|
|
5609
|
-
String?
|
|
5610
|
-
final draft = _pendingChatDraft;
|
|
5710
|
+
String? peekPendingChatDraft() {
|
|
5711
|
+
final draft = _pendingChatDraft?.trim() ?? '';
|
|
5712
|
+
return draft.isEmpty ? null : draft;
|
|
5713
|
+
}
|
|
5714
|
+
|
|
5715
|
+
List<SharedChatAttachment> peekPendingSharedChatAttachments() {
|
|
5716
|
+
return List<SharedChatAttachment>.unmodifiable(
|
|
5717
|
+
_pendingSharedChatAttachments,
|
|
5718
|
+
);
|
|
5719
|
+
}
|
|
5720
|
+
|
|
5721
|
+
void clearPendingSharedChatPayload() {
|
|
5611
5722
|
_pendingChatDraft = null;
|
|
5612
|
-
|
|
5723
|
+
_pendingSharedChatAttachments = const <SharedChatAttachment>[];
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5726
|
+
String _taskWithSharedAttachments(
|
|
5727
|
+
String task,
|
|
5728
|
+
List<SharedChatAttachment> attachments,
|
|
5729
|
+
) {
|
|
5730
|
+
final base = task.trim();
|
|
5731
|
+
if (attachments.isEmpty) {
|
|
5732
|
+
return base;
|
|
5733
|
+
}
|
|
5734
|
+
final lines = attachments
|
|
5735
|
+
.map((item) {
|
|
5736
|
+
final type = item.mimeType.trim().isEmpty
|
|
5737
|
+
? 'unknown'
|
|
5738
|
+
: item.mimeType.trim();
|
|
5739
|
+
return '- ${item.name} ($type) [local uri: ${item.uri}]';
|
|
5740
|
+
})
|
|
5741
|
+
.join('\n');
|
|
5742
|
+
final attachmentBlock = [
|
|
5743
|
+
'Shared attachments from mobile app:',
|
|
5744
|
+
lines,
|
|
5745
|
+
'Use these for context. If the local URI is not directly accessible from the server, ask me to upload the file.',
|
|
5746
|
+
].join('\n');
|
|
5747
|
+
if (base.isEmpty) {
|
|
5748
|
+
return attachmentBlock;
|
|
5749
|
+
}
|
|
5750
|
+
return '$base\n\n$attachmentBlock';
|
|
5613
5751
|
}
|
|
5614
5752
|
|
|
5615
5753
|
void selectWidget(String? widgetId) {
|
|
@@ -1321,6 +1321,32 @@ class VoiceAssistantTurnResult {
|
|
|
1321
1321
|
final String? ttsError;
|
|
1322
1322
|
}
|
|
1323
1323
|
|
|
1324
|
+
class MemoryTransferImportResult {
|
|
1325
|
+
const MemoryTransferImportResult({
|
|
1326
|
+
required this.importedCount,
|
|
1327
|
+
required this.skippedCount,
|
|
1328
|
+
required this.coreUpdatedCount,
|
|
1329
|
+
required this.behaviorNotesUpdated,
|
|
1330
|
+
required this.warnings,
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
factory MemoryTransferImportResult.fromJson(Map<dynamic, dynamic> json) {
|
|
1334
|
+
return MemoryTransferImportResult(
|
|
1335
|
+
importedCount: _asInt(json['importedCount']),
|
|
1336
|
+
skippedCount: _asInt(json['skippedCount']),
|
|
1337
|
+
coreUpdatedCount: _asInt(json['coreUpdatedCount']),
|
|
1338
|
+
behaviorNotesUpdated: json['behaviorNotesUpdated'] == true,
|
|
1339
|
+
warnings: _jsonStringList(json['warnings']),
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
final int importedCount;
|
|
1344
|
+
final int skippedCount;
|
|
1345
|
+
final int coreUpdatedCount;
|
|
1346
|
+
final bool behaviorNotesUpdated;
|
|
1347
|
+
final List<String> warnings;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1324
1350
|
class LiveVoiceBufferedChunk {
|
|
1325
1351
|
LiveVoiceBufferedChunk({
|
|
1326
1352
|
required this.sequence,
|
|
@@ -1864,6 +1890,49 @@ class ChatEntry {
|
|
|
1864
1890
|
}
|
|
1865
1891
|
}
|
|
1866
1892
|
|
|
1893
|
+
class SharedChatAttachment {
|
|
1894
|
+
const SharedChatAttachment({
|
|
1895
|
+
required this.uri,
|
|
1896
|
+
required this.name,
|
|
1897
|
+
required this.mimeType,
|
|
1898
|
+
this.sizeBytes,
|
|
1899
|
+
this.source = 'share_intent',
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
factory SharedChatAttachment.fromJson(Map<dynamic, dynamic> json) {
|
|
1903
|
+
return SharedChatAttachment(
|
|
1904
|
+
uri: json['uri']?.toString() ?? '',
|
|
1905
|
+
name: json['name']?.toString() ?? 'Attachment',
|
|
1906
|
+
mimeType:
|
|
1907
|
+
json['mimeType']?.toString().ifEmpty('application/octet-stream') ??
|
|
1908
|
+
'application/octet-stream',
|
|
1909
|
+
sizeBytes: json['sizeBytes'] is num
|
|
1910
|
+
? (json['sizeBytes'] as num).toInt()
|
|
1911
|
+
: null,
|
|
1912
|
+
source:
|
|
1913
|
+
json['source']?.toString().ifEmpty('share_intent') ?? 'share_intent',
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
final String uri;
|
|
1918
|
+
final String name;
|
|
1919
|
+
final String mimeType;
|
|
1920
|
+
final int? sizeBytes;
|
|
1921
|
+
final String source;
|
|
1922
|
+
|
|
1923
|
+
Map<String, dynamic> toJson() {
|
|
1924
|
+
return <String, dynamic>{
|
|
1925
|
+
'uri': uri,
|
|
1926
|
+
'name': name,
|
|
1927
|
+
'mimeType': mimeType,
|
|
1928
|
+
if (sizeBytes != null) 'sizeBytes': sizeBytes,
|
|
1929
|
+
'source': source,
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
bool get isValid => uri.trim().isNotEmpty;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1867
1936
|
class AgentProfile {
|
|
1868
1937
|
const AgentProfile({
|
|
1869
1938
|
required this.id,
|
|
@@ -1242,21 +1242,157 @@ class MemoryPanel extends StatefulWidget {
|
|
|
1242
1242
|
|
|
1243
1243
|
class _MemoryPanelState extends State<MemoryPanel> {
|
|
1244
1244
|
late final TextEditingController _searchController;
|
|
1245
|
+
late final TextEditingController _llmPromptController;
|
|
1246
|
+
late final TextEditingController _llmImportController;
|
|
1245
1247
|
final Set<String> _selectedMemoryIds = <String>{};
|
|
1246
1248
|
bool _bulkActionInFlight = false;
|
|
1249
|
+
bool _llmPromptLoading = false;
|
|
1250
|
+
bool _llmImporting = false;
|
|
1251
|
+
bool _llmApplyBehaviorNotes = true;
|
|
1252
|
+
bool _llmApplyCoreMemory = true;
|
|
1247
1253
|
|
|
1248
1254
|
@override
|
|
1249
1255
|
void initState() {
|
|
1250
1256
|
super.initState();
|
|
1251
1257
|
_searchController = TextEditingController();
|
|
1258
|
+
_llmPromptController = TextEditingController();
|
|
1259
|
+
_llmImportController = TextEditingController();
|
|
1252
1260
|
}
|
|
1253
1261
|
|
|
1254
1262
|
@override
|
|
1255
1263
|
void dispose() {
|
|
1256
1264
|
_searchController.dispose();
|
|
1265
|
+
_llmPromptController.dispose();
|
|
1266
|
+
_llmImportController.dispose();
|
|
1257
1267
|
super.dispose();
|
|
1258
1268
|
}
|
|
1259
1269
|
|
|
1270
|
+
Future<void> _loadLlmPrompt(NeoAgentController controller) async {
|
|
1271
|
+
if (_llmPromptLoading) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
setState(() {
|
|
1275
|
+
_llmPromptLoading = true;
|
|
1276
|
+
});
|
|
1277
|
+
try {
|
|
1278
|
+
final prompt = await controller.fetchMemoryTransferPrompt();
|
|
1279
|
+
if (!mounted) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
setState(() {
|
|
1283
|
+
_llmPromptController.text = prompt;
|
|
1284
|
+
});
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
if (!mounted) {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
1290
|
+
SnackBar(content: Text('Failed to generate prompt: $error')),
|
|
1291
|
+
);
|
|
1292
|
+
} finally {
|
|
1293
|
+
if (mounted) {
|
|
1294
|
+
setState(() {
|
|
1295
|
+
_llmPromptLoading = false;
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
Future<void> _copyLlmPrompt() async {
|
|
1302
|
+
final prompt = _llmPromptController.text.trim();
|
|
1303
|
+
if (prompt.isEmpty) {
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
await Clipboard.setData(ClipboardData(text: prompt));
|
|
1307
|
+
if (!mounted) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
ScaffoldMessenger.of(
|
|
1311
|
+
context,
|
|
1312
|
+
).showSnackBar(const SnackBar(content: Text('Prompt copied.')));
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
Future<void> _importLlmMemories(NeoAgentController controller) async {
|
|
1316
|
+
if (_llmImporting) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
final text = _llmImportController.text.trim();
|
|
1320
|
+
if (text.isEmpty) {
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
final confirmImport = await showDialog<bool>(
|
|
1324
|
+
context: context,
|
|
1325
|
+
builder: (context) {
|
|
1326
|
+
final applyTargets = <String>[
|
|
1327
|
+
if (_llmApplyBehaviorNotes) 'behavior notes',
|
|
1328
|
+
if (_llmApplyCoreMemory) 'core memory',
|
|
1329
|
+
'memories',
|
|
1330
|
+
];
|
|
1331
|
+
final targetLabel = applyTargets.join(', ');
|
|
1332
|
+
return AlertDialog(
|
|
1333
|
+
backgroundColor: _bgCard,
|
|
1334
|
+
title: Text('Import memory transfer?'),
|
|
1335
|
+
content: Text(
|
|
1336
|
+
'This will import the response into $targetLabel.',
|
|
1337
|
+
),
|
|
1338
|
+
actions: <Widget>[
|
|
1339
|
+
TextButton(
|
|
1340
|
+
onPressed: () => Navigator.of(context).pop(false),
|
|
1341
|
+
child: Text('Cancel'),
|
|
1342
|
+
),
|
|
1343
|
+
FilledButton(
|
|
1344
|
+
onPressed: () => Navigator.of(context).pop(true),
|
|
1345
|
+
child: Text('Import'),
|
|
1346
|
+
),
|
|
1347
|
+
],
|
|
1348
|
+
);
|
|
1349
|
+
},
|
|
1350
|
+
);
|
|
1351
|
+
if (confirmImport != true) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
setState(() {
|
|
1355
|
+
_llmImporting = true;
|
|
1356
|
+
});
|
|
1357
|
+
try {
|
|
1358
|
+
final result = await controller.importMemoryTransfer(
|
|
1359
|
+
text,
|
|
1360
|
+
applyBehaviorNotes: _llmApplyBehaviorNotes,
|
|
1361
|
+
applyCoreMemory: _llmApplyCoreMemory,
|
|
1362
|
+
);
|
|
1363
|
+
if (!mounted) {
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
_llmImportController.clear();
|
|
1367
|
+
final warningText = result.warnings.isEmpty
|
|
1368
|
+
? ''
|
|
1369
|
+
: ' ${result.warnings.join(' ')}';
|
|
1370
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
1371
|
+
SnackBar(
|
|
1372
|
+
content: Text(
|
|
1373
|
+
'Imported ${result.importedCount} memories, '
|
|
1374
|
+
'${result.coreUpdatedCount} core entries.'
|
|
1375
|
+
'${result.behaviorNotesUpdated ? ' Behavior notes updated.' : ''}'
|
|
1376
|
+
'$warningText',
|
|
1377
|
+
),
|
|
1378
|
+
),
|
|
1379
|
+
);
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
if (!mounted) {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
1385
|
+
SnackBar(content: Text('Import failed: $error')),
|
|
1386
|
+
);
|
|
1387
|
+
} finally {
|
|
1388
|
+
if (mounted) {
|
|
1389
|
+
setState(() {
|
|
1390
|
+
_llmImporting = false;
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1260
1396
|
List<MemoryItem> get _visibleMemories {
|
|
1261
1397
|
final controller = widget.controller;
|
|
1262
1398
|
return controller.memoryRecallResults.isNotEmpty
|
|
@@ -1473,6 +1609,118 @@ class _MemoryPanelState extends State<MemoryPanel> {
|
|
|
1473
1609
|
),
|
|
1474
1610
|
),
|
|
1475
1611
|
const SizedBox(height: 16),
|
|
1612
|
+
Card(
|
|
1613
|
+
child: Theme(
|
|
1614
|
+
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
|
|
1615
|
+
child: ExpansionTile(
|
|
1616
|
+
initiallyExpanded: false,
|
|
1617
|
+
tilePadding: const EdgeInsets.symmetric(
|
|
1618
|
+
horizontal: 18,
|
|
1619
|
+
vertical: 6,
|
|
1620
|
+
),
|
|
1621
|
+
childrenPadding: const EdgeInsets.fromLTRB(18, 0, 18, 18),
|
|
1622
|
+
leading: Icon(Icons.swap_horiz_outlined, color: _textSecondary),
|
|
1623
|
+
title: const _SectionTitle('LLM Memory Transfer'),
|
|
1624
|
+
subtitle: Text(
|
|
1625
|
+
'Export/import memories with another AI in one shot.',
|
|
1626
|
+
style: TextStyle(color: _textSecondary),
|
|
1627
|
+
),
|
|
1628
|
+
children: <Widget>[
|
|
1629
|
+
Text(
|
|
1630
|
+
'Generate a prompt to use in another AI, then paste the response here to import memories.',
|
|
1631
|
+
style: TextStyle(color: _textSecondary),
|
|
1632
|
+
),
|
|
1633
|
+
const SizedBox(height: 12),
|
|
1634
|
+
Wrap(
|
|
1635
|
+
spacing: 10,
|
|
1636
|
+
runSpacing: 10,
|
|
1637
|
+
children: <Widget>[
|
|
1638
|
+
FilledButton.icon(
|
|
1639
|
+
onPressed: _llmPromptLoading
|
|
1640
|
+
? null
|
|
1641
|
+
: () => _loadLlmPrompt(controller),
|
|
1642
|
+
icon: Icon(Icons.auto_awesome_outlined),
|
|
1643
|
+
label: Text(
|
|
1644
|
+
_llmPromptLoading ? 'Generating...' : 'Generate Prompt',
|
|
1645
|
+
),
|
|
1646
|
+
),
|
|
1647
|
+
OutlinedButton.icon(
|
|
1648
|
+
onPressed: _llmPromptController.text.trim().isEmpty
|
|
1649
|
+
? null
|
|
1650
|
+
: _copyLlmPrompt,
|
|
1651
|
+
icon: Icon(Icons.copy_all_outlined),
|
|
1652
|
+
label: Text('Copy Prompt'),
|
|
1653
|
+
),
|
|
1654
|
+
],
|
|
1655
|
+
),
|
|
1656
|
+
const SizedBox(height: 12),
|
|
1657
|
+
TextField(
|
|
1658
|
+
controller: _llmPromptController,
|
|
1659
|
+
minLines: 6,
|
|
1660
|
+
maxLines: 10,
|
|
1661
|
+
readOnly: true,
|
|
1662
|
+
decoration: const InputDecoration(
|
|
1663
|
+
labelText: 'Prompt to paste into another AI',
|
|
1664
|
+
),
|
|
1665
|
+
),
|
|
1666
|
+
const SizedBox(height: 12),
|
|
1667
|
+
SwitchListTile.adaptive(
|
|
1668
|
+
contentPadding: EdgeInsets.zero,
|
|
1669
|
+
value: _llmApplyBehaviorNotes,
|
|
1670
|
+
onChanged: _llmImporting
|
|
1671
|
+
? null
|
|
1672
|
+
: (value) {
|
|
1673
|
+
setState(() {
|
|
1674
|
+
_llmApplyBehaviorNotes = value;
|
|
1675
|
+
});
|
|
1676
|
+
},
|
|
1677
|
+
title: Text('Apply behavior notes'),
|
|
1678
|
+
subtitle: Text(
|
|
1679
|
+
'Overwrite assistant behavior notes from the import.',
|
|
1680
|
+
),
|
|
1681
|
+
),
|
|
1682
|
+
SwitchListTile.adaptive(
|
|
1683
|
+
contentPadding: EdgeInsets.zero,
|
|
1684
|
+
value: _llmApplyCoreMemory,
|
|
1685
|
+
onChanged: _llmImporting
|
|
1686
|
+
? null
|
|
1687
|
+
: (value) {
|
|
1688
|
+
setState(() {
|
|
1689
|
+
_llmApplyCoreMemory = value;
|
|
1690
|
+
});
|
|
1691
|
+
},
|
|
1692
|
+
title: Text('Apply core memory'),
|
|
1693
|
+
subtitle: Text(
|
|
1694
|
+
'Update core memory key/value entries from the import.',
|
|
1695
|
+
),
|
|
1696
|
+
),
|
|
1697
|
+
const SizedBox(height: 16),
|
|
1698
|
+
Text(
|
|
1699
|
+
'Paste the response from the other AI below, then import.',
|
|
1700
|
+
style: TextStyle(color: _textSecondary),
|
|
1701
|
+
),
|
|
1702
|
+
const SizedBox(height: 12),
|
|
1703
|
+
TextField(
|
|
1704
|
+
controller: _llmImportController,
|
|
1705
|
+
minLines: 6,
|
|
1706
|
+
maxLines: 12,
|
|
1707
|
+
decoration: const InputDecoration(
|
|
1708
|
+
labelText: 'LLM memory export response',
|
|
1709
|
+
),
|
|
1710
|
+
),
|
|
1711
|
+
const SizedBox(height: 12),
|
|
1712
|
+
FilledButton.icon(
|
|
1713
|
+
onPressed: _llmImporting
|
|
1714
|
+
? null
|
|
1715
|
+
: () => _importLlmMemories(controller),
|
|
1716
|
+
icon: Icon(Icons.file_download_outlined),
|
|
1717
|
+
label: Text(_llmImporting ? 'Importing...' : 'Import'),
|
|
1718
|
+
),
|
|
1719
|
+
],
|
|
1720
|
+
),
|
|
1721
|
+
),
|
|
1722
|
+
),
|
|
1723
|
+
const SizedBox(height: 16),
|
|
1476
1724
|
Card(
|
|
1477
1725
|
child: Padding(
|
|
1478
1726
|
padding: const EdgeInsets.all(18),
|
|
@@ -14,7 +14,7 @@ class _NeoAgentAppState extends State<NeoAgentApp>
|
|
|
14
14
|
late final NeoAgentController _controller;
|
|
15
15
|
late final WebAppUpdateMonitor _webAppUpdateMonitor;
|
|
16
16
|
final AppLaunchBridge _appLaunchBridge = AppLaunchBridge();
|
|
17
|
-
StreamSubscription<
|
|
17
|
+
StreamSubscription<AppLaunchRequest>? _appLaunchSubscription;
|
|
18
18
|
StreamSubscription<String>? _widgetOpenSubscription;
|
|
19
19
|
GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
|
20
20
|
String? _navigatorScopeSignature;
|
|
@@ -93,9 +93,18 @@ class _NeoAgentAppState extends State<NeoAgentApp>
|
|
|
93
93
|
unawaited(_syncDesktopShell());
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
void _handleAppLaunchRequest(
|
|
96
|
+
void _handleAppLaunchRequest(AppLaunchRequest request) {
|
|
97
|
+
final action = request.action;
|
|
97
98
|
if (action == AppLaunchBridge.voiceAssistantAction) {
|
|
98
99
|
_controller.openVoiceAssistantSurface();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (action == AppLaunchBridge.shareToChatAction) {
|
|
103
|
+
_controller.queueSharedChatPayload(
|
|
104
|
+
text: request.text,
|
|
105
|
+
subject: request.subject,
|
|
106
|
+
files: request.files,
|
|
107
|
+
);
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
110
|
|