neoagent 3.1.1-beta.9 → 3.2.0

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.
Files changed (150) hide show
  1. package/.env.example +2 -2
  2. package/README.md +5 -1
  3. package/docs/architecture.md +1 -1
  4. package/docs/benchmarking.md +68 -87
  5. package/docs/capabilities.md +1 -1
  6. package/docs/clients-and-devices.md +3 -4
  7. package/docs/configuration.md +1 -1
  8. package/docs/devices.md +1 -2
  9. package/docs/health.md +24 -0
  10. package/docs/index.md +2 -2
  11. package/docs/integrations-architecture.md +6 -0
  12. package/docs/integrations.md +15 -1
  13. package/docs/models.md +1 -1
  14. package/docs/persistence.md +1 -1
  15. package/docs/why-neoagent.md +1 -1
  16. package/flutter_app/README.md +0 -5
  17. package/flutter_app/android/app/src/main/AndroidManifest.xml +0 -14
  18. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/MainActivity.kt +0 -98
  19. package/flutter_app/lib/main.dart +0 -6
  20. package/flutter_app/lib/main_app_shell.dart +0 -2
  21. package/flutter_app/lib/main_controller.dart +2 -867
  22. package/flutter_app/lib/main_devices.dart +0 -73
  23. package/flutter_app/lib/main_integrations.dart +339 -187
  24. package/flutter_app/lib/main_launcher.dart +2 -212
  25. package/flutter_app/lib/main_models.dart +0 -259
  26. package/flutter_app/lib/main_navigation.dart +2 -14
  27. package/flutter_app/lib/main_operations.dart +322 -364
  28. package/flutter_app/lib/main_runtime.dart +9 -208
  29. package/flutter_app/lib/main_settings.dart +17 -199
  30. package/flutter_app/lib/main_shared.dart +20 -591
  31. package/flutter_app/lib/main_timeline.dart +26 -122
  32. package/flutter_app/lib/main_voice_assistant.dart +0 -5
  33. package/flutter_app/lib/src/backend_client.dart +0 -102
  34. package/flutter_app/lib/src/desktop_companion_io.dart +1 -65
  35. package/flutter_app/lib/src/desktop_companion_stub.dart +0 -12
  36. package/flutter_app/linux/flutter/generated_plugin_registrant.cc +0 -4
  37. package/flutter_app/linux/flutter/generated_plugins.cmake +0 -1
  38. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +0 -2
  39. package/flutter_app/macos/Podfile.lock +56 -5
  40. package/flutter_app/pubspec.lock +0 -7
  41. package/flutter_app/pubspec.yaml +0 -2
  42. package/flutter_app/web/index.html +174 -0
  43. package/flutter_app/windows/flutter/generated_plugin_registrant.cc +0 -3
  44. package/flutter_app/windows/flutter/generated_plugins.cmake +0 -1
  45. package/landing/index.html +0 -2
  46. package/lib/manager.js +1 -1
  47. package/lib/schema_migrations.js +75 -0
  48. package/package.json +1 -4
  49. package/server/db/database.js +1 -133
  50. package/server/http/middleware.js +2 -16
  51. package/server/http/routes.js +0 -2
  52. package/server/public/.last_build_id +1 -1
  53. package/server/public/assets/NOTICES +0 -25
  54. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  55. package/server/public/flutter_bootstrap.js +1 -1
  56. package/server/public/index.html +174 -0
  57. package/server/public/main.dart.js +88081 -90036
  58. package/server/routes/auth.js +11 -2
  59. package/server/routes/settings.js +0 -5
  60. package/server/routes/voice_assistant.js +1 -128
  61. package/server/services/account/erasure.js +0 -3
  62. package/server/services/ai/deliverables/artifact_helpers.js +1 -1
  63. package/server/services/ai/loop/conversation_loop.js +6 -2
  64. package/server/services/ai/loop/tool_dispatch.js +0 -3
  65. package/server/services/ai/messagingFallback.js +1 -1
  66. package/server/services/ai/settings.js +0 -6
  67. package/server/services/ai/toolEvidence.js +1 -4
  68. package/server/services/ai/tools.js +0 -234
  69. package/server/services/desktop/auth.js +0 -3
  70. package/server/services/desktop/registry.js +2 -50
  71. package/server/services/integrations/neoarchive/provider.js +616 -0
  72. package/server/services/integrations/neorecall/client.js +140 -0
  73. package/server/services/integrations/neorecall/constants.js +87 -0
  74. package/server/services/integrations/neorecall/provider.js +163 -0
  75. package/server/services/integrations/registry.js +4 -2
  76. package/server/services/manager.js +0 -22
  77. package/server/services/memory/manager.js +44 -19
  78. package/server/services/memory/retrieval_reasoning.js +36 -14
  79. package/server/services/messaging/whatsapp.js +18 -0
  80. package/server/services/security/tool_categories.js +0 -5
  81. package/server/services/social_video/service.js +2 -20
  82. package/server/services/tasks/adapters/index.js +1 -1
  83. package/server/services/tasks/integration_runtime.js +3 -67
  84. package/server/services/tasks/runtime.js +1 -6
  85. package/server/services/tasks/task_repository.js +6 -13
  86. package/server/services/timeline/service.js +0 -224
  87. package/server/services/voice/message.js +0 -24
  88. package/server/services/voice/providers.js +42 -1
  89. package/server/services/voice/runtimeManager.js +9 -5
  90. package/server/services/voice/turnRunner.js +0 -22
  91. package/server/services/wearable/service.js +0 -5
  92. package/docs/recordings-and-health.md +0 -41
  93. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingBootReceiver.kt +0 -21
  94. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingForegroundService.kt +0 -586
  95. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingStateStore.kt +0 -78
  96. package/flutter_app/android/app/src/main/kotlin/com/neoagent/flutter_app/recording/RecordingUploadClient.kt +0 -104
  97. package/flutter_app/lib/main_recordings.dart +0 -920
  98. package/flutter_app/lib/src/desktop_ocr_bridge.dart +0 -2
  99. package/flutter_app/lib/src/desktop_ocr_bridge_io.dart +0 -125
  100. package/flutter_app/lib/src/desktop_ocr_bridge_stub.dart +0 -30
  101. package/flutter_app/lib/src/desktop_passive_history.dart +0 -332
  102. package/flutter_app/lib/src/recording_bridge.dart +0 -232
  103. package/flutter_app/lib/src/recording_bridge_io.dart +0 -1027
  104. package/flutter_app/lib/src/recording_bridge_stub.dart +0 -120
  105. package/flutter_app/lib/src/recording_bridge_web.dart +0 -702
  106. package/flutter_app/lib/src/recording_chunk_queue.dart +0 -149
  107. package/flutter_app/lib/src/recording_chunk_queue_io.dart +0 -182
  108. package/flutter_app/lib/src/recording_payloads.dart +0 -95
  109. package/flutter_app/third_party/desktop_audio_capture/LICENSE +0 -21
  110. package/flutter_app/third_party/desktop_audio_capture/README.md +0 -262
  111. package/flutter_app/third_party/desktop_audio_capture/lib/audio_capture.dart +0 -65
  112. package/flutter_app/third_party/desktop_audio_capture/lib/config/mic_audio_config.dart +0 -153
  113. package/flutter_app/third_party/desktop_audio_capture/lib/config/system_adudio_config.dart +0 -110
  114. package/flutter_app/third_party/desktop_audio_capture/lib/mic/mic_audio_capture.dart +0 -461
  115. package/flutter_app/third_party/desktop_audio_capture/lib/model/audio_status.dart +0 -91
  116. package/flutter_app/third_party/desktop_audio_capture/lib/model/decibel_data.dart +0 -106
  117. package/flutter_app/third_party/desktop_audio_capture/lib/model/input_device_type.dart +0 -219
  118. package/flutter_app/third_party/desktop_audio_capture/lib/system/system_audio_capture.dart +0 -336
  119. package/flutter_app/third_party/desktop_audio_capture/linux/CMakeLists.txt +0 -101
  120. package/flutter_app/third_party/desktop_audio_capture/linux/audio_capture_plugin.cc +0 -692
  121. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/audio_capture_plugin.h +0 -35
  122. package/flutter_app/third_party/desktop_audio_capture/linux/include/audio_capture/mic_capture_plugin.h +0 -36
  123. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/audio_capture_plugin.h +0 -32
  124. package/flutter_app/third_party/desktop_audio_capture/linux/include/desktop_audio_capture/mic_capture_plugin.h +0 -32
  125. package/flutter_app/third_party/desktop_audio_capture/linux/mic_capture_plugin.cc +0 -878
  126. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/AudioCapturePlugin.swift +0 -27
  127. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/MicCapturePlugin.swift +0 -1172
  128. package/flutter_app/third_party/desktop_audio_capture/macos/Classes/SystemCapturePlugin.swift +0 -655
  129. package/flutter_app/third_party/desktop_audio_capture/macos/Resources/PrivacyInfo.xcprivacy +0 -12
  130. package/flutter_app/third_party/desktop_audio_capture/macos/desktop_audio_capture.podspec +0 -30
  131. package/flutter_app/third_party/desktop_audio_capture/pubspec.yaml +0 -87
  132. package/flutter_app/third_party/desktop_audio_capture/windows/CMakeLists.txt +0 -105
  133. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.cpp +0 -80
  134. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin.h +0 -31
  135. package/flutter_app/third_party/desktop_audio_capture/windows/audio_capture_plugin_c_api.cpp +0 -12
  136. package/flutter_app/third_party/desktop_audio_capture/windows/include/audio_capture/audio_capture_plugin_c_api.h +0 -23
  137. package/flutter_app/third_party/desktop_audio_capture/windows/include/desktop_audio_capture/audio_capture_plugin.h +0 -25
  138. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.cpp +0 -1117
  139. package/flutter_app/third_party/desktop_audio_capture/windows/mic_capture_plugin.h +0 -115
  140. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.cpp +0 -777
  141. package/flutter_app/third_party/desktop_audio_capture/windows/system_audio_capture_plugin.h +0 -87
  142. package/server/routes/recordings.js +0 -335
  143. package/server/routes/screenHistory.js +0 -190
  144. package/server/services/ai/recordingInsights.js +0 -105
  145. package/server/services/integrations/neomail/provider.js +0 -993
  146. package/server/services/recordings/deepgram.js +0 -58
  147. package/server/services/recordings/manager.js +0 -1273
  148. package/server/services/tasks/adapters/neomail_email_received.js +0 -46
  149. package/server/services/voice/screenshotContext.js +0 -73
  150. package/server/services/voice/turnCoordinator.js +0 -51
@@ -418,7 +418,6 @@ List<AppSection> _mainSections(NeoAgentController controller) {
418
418
  return <AppSection>[
419
419
  AppSection.chat,
420
420
  AppSection.timeline,
421
- AppSection.recordings,
422
421
  AppSection.devices,
423
422
  AppSection.tasks,
424
423
  AppSection.widgets,
@@ -2416,63 +2415,6 @@ class _DesktopCloseDecision {
2416
2415
  final bool rememberChoice;
2417
2416
  }
2418
2417
 
2419
- class _RecordingPermissionBadge extends StatelessWidget {
2420
- const _RecordingPermissionBadge({required this.label, required this.state});
2421
-
2422
- final String label;
2423
- final RecordingPermissionState state;
2424
-
2425
- @override
2426
- Widget build(BuildContext context) {
2427
- final (color, icon, text) = switch (state) {
2428
- RecordingPermissionState.granted => (
2429
- _success,
2430
- Icons.check_circle,
2431
- 'Ready',
2432
- ),
2433
- RecordingPermissionState.denied => (
2434
- _danger,
2435
- Icons.lock_outline,
2436
- 'Blocked',
2437
- ),
2438
- RecordingPermissionState.needsRestart => (
2439
- _warning,
2440
- Icons.restart_alt_rounded,
2441
- 'Restart needed',
2442
- ),
2443
- RecordingPermissionState.unsupported => (
2444
- _textSecondary,
2445
- Icons.do_not_disturb_alt_outlined,
2446
- 'Unsupported',
2447
- ),
2448
- RecordingPermissionState.unknown => (
2449
- _warning,
2450
- Icons.help_outline,
2451
- 'Check access',
2452
- ),
2453
- };
2454
-
2455
- return _GlassSurface(
2456
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
2457
- borderRadius: BorderRadius.circular(16),
2458
- blurSigma: 10,
2459
- fillColor: color.withValues(alpha: 0.09),
2460
- borderColor: color.withValues(alpha: 0.22),
2461
- child: Row(
2462
- mainAxisSize: MainAxisSize.min,
2463
- children: <Widget>[
2464
- Icon(icon, size: 16, color: color),
2465
- const SizedBox(width: 8),
2466
- Text(
2467
- '$label · $text',
2468
- style: TextStyle(color: color, fontWeight: FontWeight.w700),
2469
- ),
2470
- ],
2471
- ),
2472
- );
2473
- }
2474
- }
2475
-
2476
2418
  class _CompanionPermissionBadge extends StatelessWidget {
2477
2419
  const _CompanionPermissionBadge({required this.label, required this.state});
2478
2420
 
@@ -2514,254 +2456,6 @@ class _CompanionPermissionBadge extends StatelessWidget {
2514
2456
  }
2515
2457
  }
2516
2458
 
2517
- class _AudioLevelBar extends StatelessWidget {
2518
- const _AudioLevelBar({
2519
- required this.label,
2520
- required this.valueDb,
2521
- required this.color,
2522
- this.compact = false,
2523
- });
2524
-
2525
- final String label;
2526
- final double valueDb;
2527
- final Color color;
2528
- final bool compact;
2529
-
2530
- @override
2531
- Widget build(BuildContext context) {
2532
- final progress = ((valueDb + 72) / 72).clamp(0.0, 1.0);
2533
- return _GlassSurface(
2534
- width: compact ? 168 : 240,
2535
- padding: const EdgeInsets.all(12),
2536
- borderRadius: BorderRadius.circular(18),
2537
- blurSigma: 10,
2538
- fillColor: _bgCard.withValues(alpha: 0.88),
2539
- borderColor: _borderLight,
2540
- child: Column(
2541
- crossAxisAlignment: CrossAxisAlignment.start,
2542
- children: <Widget>[
2543
- Row(
2544
- children: <Widget>[
2545
- Text(
2546
- label,
2547
- style: TextStyle(
2548
- color: _textSecondary,
2549
- fontSize: compact ? 11 : 12,
2550
- fontWeight: FontWeight.w700,
2551
- letterSpacing: 0.3,
2552
- ),
2553
- ),
2554
- const Spacer(),
2555
- Text(
2556
- valueDb <= -119 ? 'Silent' : '${valueDb.toStringAsFixed(0)} dB',
2557
- style: TextStyle(
2558
- color: _textSecondary,
2559
- fontSize: compact ? 11 : 12,
2560
- ),
2561
- ),
2562
- ],
2563
- ),
2564
- const SizedBox(height: 10),
2565
- ClipRRect(
2566
- borderRadius: BorderRadius.circular(999),
2567
- child: TweenAnimationBuilder<double>(
2568
- tween: Tween<double>(begin: 0, end: progress),
2569
- duration: const Duration(milliseconds: 220),
2570
- curve: Curves.easeOutCubic,
2571
- builder: (context, animatedValue, _) {
2572
- return LinearProgressIndicator(
2573
- value: animatedValue,
2574
- minHeight: compact ? 7 : 8,
2575
- color: color,
2576
- backgroundColor: _borderLight,
2577
- );
2578
- },
2579
- ),
2580
- ),
2581
- ],
2582
- ),
2583
- );
2584
- }
2585
- }
2586
-
2587
- class _DesktopFloatingToolbar extends StatefulWidget {
2588
- const _DesktopFloatingToolbar({required this.controller});
2589
-
2590
- final NeoAgentController controller;
2591
-
2592
- @override
2593
- State<_DesktopFloatingToolbar> createState() =>
2594
- _DesktopFloatingToolbarState();
2595
- }
2596
-
2597
- class _DesktopFloatingToolbarState extends State<_DesktopFloatingToolbar> {
2598
- Timer? _ticker;
2599
-
2600
- @override
2601
- void initState() {
2602
- super.initState();
2603
- _syncTicker();
2604
- }
2605
-
2606
- @override
2607
- void didUpdateWidget(covariant _DesktopFloatingToolbar oldWidget) {
2608
- super.didUpdateWidget(oldWidget);
2609
- _syncTicker();
2610
- }
2611
-
2612
- @override
2613
- void dispose() {
2614
- _ticker?.cancel();
2615
- super.dispose();
2616
- }
2617
-
2618
- void _syncTicker() {
2619
- final runtime = widget.controller.recordingRuntime;
2620
- final shouldTick =
2621
- runtime.active &&
2622
- runtime.startedAt != null &&
2623
- runtime.floatingToolbarVisible;
2624
- if (!shouldTick) {
2625
- _ticker?.cancel();
2626
- _ticker = null;
2627
- return;
2628
- }
2629
- _ticker ??= Timer.periodic(const Duration(seconds: 1), (_) {
2630
- if (mounted) {
2631
- setState(() {});
2632
- }
2633
- });
2634
- }
2635
-
2636
- @override
2637
- Widget build(BuildContext context) {
2638
- final controller = widget.controller;
2639
- final runtime = controller.recordingRuntime;
2640
- if (!runtime.supportsFloatingToolbar ||
2641
- !runtime.active ||
2642
- !runtime.floatingToolbarVisible) {
2643
- return const SizedBox.shrink();
2644
- }
2645
-
2646
- final elapsed = runtime.startedAt == null
2647
- ? '00:00'
2648
- : _formatDuration(
2649
- DateTime.now().difference(runtime.startedAt!).inMilliseconds,
2650
- );
2651
-
2652
- return Positioned(
2653
- top: 10,
2654
- left: 0,
2655
- right: 0,
2656
- child: SafeArea(
2657
- child: IgnorePointer(
2658
- ignoring: false,
2659
- child: Align(
2660
- alignment: Alignment.topCenter,
2661
- child: _DesktopFloatingToolbarSurface(
2662
- controller: controller,
2663
- elapsedLabel: elapsed,
2664
- compactWindow: false,
2665
- onOpenMainWindow: null,
2666
- ),
2667
- ),
2668
- ),
2669
- ),
2670
- );
2671
- }
2672
- }
2673
-
2674
- class _DetachedDesktopFloatingToolbarShell extends StatefulWidget {
2675
- const _DetachedDesktopFloatingToolbarShell({
2676
- required this.controller,
2677
- required this.onOpenMainWindow,
2678
- });
2679
-
2680
- final NeoAgentController controller;
2681
- final Future<void> Function() onOpenMainWindow;
2682
-
2683
- @override
2684
- State<_DetachedDesktopFloatingToolbarShell> createState() =>
2685
- _DetachedDesktopFloatingToolbarShellState();
2686
- }
2687
-
2688
- class _DetachedDesktopFloatingToolbarShellState
2689
- extends State<_DetachedDesktopFloatingToolbarShell> {
2690
- Timer? _ticker;
2691
-
2692
- @override
2693
- void initState() {
2694
- super.initState();
2695
- _syncTicker();
2696
- }
2697
-
2698
- @override
2699
- void didUpdateWidget(
2700
- covariant _DetachedDesktopFloatingToolbarShell oldWidget,
2701
- ) {
2702
- super.didUpdateWidget(oldWidget);
2703
- _syncTicker();
2704
- }
2705
-
2706
- @override
2707
- void dispose() {
2708
- _ticker?.cancel();
2709
- super.dispose();
2710
- }
2711
-
2712
- void _syncTicker() {
2713
- final runtime = widget.controller.recordingRuntime;
2714
- final shouldTick =
2715
- runtime.active &&
2716
- runtime.startedAt != null &&
2717
- runtime.floatingToolbarVisible;
2718
- if (!shouldTick) {
2719
- _ticker?.cancel();
2720
- _ticker = null;
2721
- return;
2722
- }
2723
- _ticker ??= Timer.periodic(const Duration(seconds: 1), (_) {
2724
- if (mounted) {
2725
- setState(() {});
2726
- }
2727
- });
2728
- }
2729
-
2730
- @override
2731
- Widget build(BuildContext context) {
2732
- final runtime = widget.controller.recordingRuntime;
2733
- if (!runtime.active || !runtime.floatingToolbarVisible) {
2734
- return const SizedBox.shrink();
2735
- }
2736
-
2737
- final elapsed = runtime.startedAt == null
2738
- ? '00:00'
2739
- : _formatDuration(
2740
- DateTime.now().difference(runtime.startedAt!).inMilliseconds,
2741
- );
2742
-
2743
- return DecoratedBox(
2744
- decoration: const BoxDecoration(color: Colors.transparent),
2745
- child: Scaffold(
2746
- backgroundColor: Colors.transparent,
2747
- body: SafeArea(
2748
- child: Center(
2749
- child: Padding(
2750
- padding: const EdgeInsets.all(10),
2751
- child: _DesktopFloatingToolbarSurface(
2752
- controller: widget.controller,
2753
- elapsedLabel: elapsed,
2754
- compactWindow: true,
2755
- onOpenMainWindow: widget.onOpenMainWindow,
2756
- ),
2757
- ),
2758
- ),
2759
- ),
2760
- ),
2761
- );
2762
- }
2763
- }
2764
-
2765
2459
  bool _desktopAssistantUsesToggleControls() {
2766
2460
  if (kIsWeb) {
2767
2461
  return false;
@@ -2802,10 +2496,6 @@ String _desktopAssistantIdleHint() {
2802
2496
  : 'Hold Ctrl+Shift+Space to talk';
2803
2497
  }
2804
2498
 
2805
- String _desktopAssistantScreenContextHint(bool enabled) {
2806
- return enabled ? 'Current screen will be attached' : 'Audio only';
2807
- }
2808
-
2809
2499
  class _DesktopAssistantControlState {
2810
2500
  const _DesktopAssistantControlState({
2811
2501
  required this.isCapturing,
@@ -2819,17 +2509,13 @@ class _DesktopAssistantControlState {
2819
2509
  required this.primaryIcon,
2820
2510
  required this.primaryColor,
2821
2511
  required this.idleHint,
2822
- required this.screenContextHint,
2823
- required this.sourceSummary,
2824
2512
  });
2825
2513
 
2826
2514
  factory _DesktopAssistantControlState.fromController(
2827
- NeoAgentController controller, {
2828
- required bool blockedHintVisible,
2829
- }) {
2515
+ NeoAgentController controller,
2516
+ ) {
2830
2517
  final liveState = controller.voiceAssistantLiveState;
2831
2518
  final isCapturing = controller.isLiveVoiceCaptureEngaged;
2832
- final includeScreenContext = controller.voiceAssistantIncludeScreenContext;
2833
2519
  final useToggleCapture = _desktopAssistantUsesToggleControls();
2834
2520
  final transcriptPreview = liveState.partialTranscript.trim().isEmpty
2835
2521
  ? liveState.finalTranscript.trim()
@@ -2838,24 +2524,16 @@ class _DesktopAssistantControlState {
2838
2524
  isCapturing: isCapturing,
2839
2525
  isBusy: liveState.isBusy,
2840
2526
  useToggleCapture: useToggleCapture,
2841
- statusLabel: blockedHintVisible
2842
- ? 'Assistant unavailable while recording'
2843
- : (isCapturing
2844
- ? _desktopAssistantPrimaryLabel(true)
2845
- : _desktopAssistantStatusLabel(liveState.state)),
2846
- statusColor: blockedHintVisible
2847
- ? _warning
2848
- : (isCapturing ? _success : _accent),
2527
+ statusLabel: isCapturing
2528
+ ? _desktopAssistantPrimaryLabel(true)
2529
+ : _desktopAssistantStatusLabel(liveState.state),
2530
+ statusColor: isCapturing ? _success : _accent,
2849
2531
  transcriptPreview: transcriptPreview,
2850
2532
  primaryLabel: _desktopAssistantPrimaryLabel(isCapturing),
2851
2533
  primaryCaption: _desktopAssistantPrimaryCaption(isCapturing),
2852
2534
  primaryIcon: isCapturing ? Icons.stop_rounded : Icons.mic,
2853
2535
  primaryColor: isCapturing ? _warning : _success,
2854
2536
  idleHint: _desktopAssistantIdleHint(),
2855
- screenContextHint: _desktopAssistantScreenContextHint(
2856
- includeScreenContext,
2857
- ),
2858
- sourceSummary: includeScreenContext ? 'Mic + screen' : 'Direct mic',
2859
2537
  );
2860
2538
  }
2861
2539
 
@@ -2870,72 +2548,16 @@ class _DesktopAssistantControlState {
2870
2548
  final IconData primaryIcon;
2871
2549
  final Color primaryColor;
2872
2550
  final String idleHint;
2873
- final String screenContextHint;
2874
- final String sourceSummary;
2875
- }
2876
-
2877
- class _VoiceAssistantScreenContextButton extends StatelessWidget {
2878
- const _VoiceAssistantScreenContextButton({
2879
- required this.controller,
2880
- required this.compact,
2881
- });
2882
-
2883
- final NeoAgentController controller;
2884
- final bool compact;
2885
-
2886
- @override
2887
- Widget build(BuildContext context) {
2888
- final enabled = controller.voiceAssistantIncludeScreenContext;
2889
- final onPressed = controller.canCaptureVoiceAssistantScreenContext
2890
- ? () {
2891
- unawaited(controller.toggleVoiceAssistantScreenContext());
2892
- }
2893
- : null;
2894
-
2895
- if (compact) {
2896
- return IconButton(
2897
- tooltip: enabled
2898
- ? 'Stop including the current screen'
2899
- : 'Include the current screen',
2900
- onPressed: onPressed,
2901
- style: IconButton.styleFrom(
2902
- visualDensity: VisualDensity.compact,
2903
- padding: const EdgeInsets.all(8),
2904
- minimumSize: const Size(30, 30),
2905
- backgroundColor: enabled
2906
- ? _accent.withValues(alpha: 0.14)
2907
- : _bgSecondary.withValues(alpha: 0.9),
2908
- foregroundColor: enabled ? _accent : _textSecondary,
2909
- ),
2910
- icon: Icon(
2911
- enabled
2912
- ? Icons.desktop_windows_rounded
2913
- : Icons.desktop_windows_outlined,
2914
- size: 15,
2915
- ),
2916
- );
2917
- }
2918
-
2919
- return _VoiceAssistantActionButton(
2920
- icon: enabled
2921
- ? Icons.desktop_windows_rounded
2922
- : Icons.desktop_windows_outlined,
2923
- label: enabled ? 'Screen on' : 'Screen off',
2924
- onTap: onPressed,
2925
- );
2926
- }
2927
2551
  }
2928
2552
 
2929
2553
  class _DesktopAssistantPopupShell extends StatelessWidget {
2930
2554
  const _DesktopAssistantPopupShell({
2931
2555
  required this.controller,
2932
- required this.blockedHintVisible,
2933
2556
  required this.onPrimaryAction,
2934
2557
  required this.onCancel,
2935
2558
  });
2936
2559
 
2937
2560
  final NeoAgentController controller;
2938
- final bool blockedHintVisible;
2939
2561
  final Future<void> Function() onPrimaryAction;
2940
2562
  final Future<void> Function() onCancel;
2941
2563
 
@@ -2943,7 +2565,6 @@ class _DesktopAssistantPopupShell extends StatelessWidget {
2943
2565
  Widget build(BuildContext context) {
2944
2566
  final assistantUi = _DesktopAssistantControlState.fromController(
2945
2567
  controller,
2946
- blockedHintVisible: blockedHintVisible,
2947
2568
  );
2948
2569
 
2949
2570
  return DecoratedBox(
@@ -3011,34 +2632,26 @@ class _DesktopAssistantPopupShell extends StatelessWidget {
3011
2632
  fontSize: 12.5,
3012
2633
  ),
3013
2634
  ),
3014
- if (!blockedHintVisible)
3015
- Text(
3016
- assistantUi.transcriptPreview.isEmpty
3017
- ? '${assistantUi.idleHint} • ${assistantUi.screenContextHint}'
3018
- : assistantUi.transcriptPreview,
3019
- maxLines: 1,
3020
- overflow: TextOverflow.ellipsis,
3021
- style: TextStyle(
3022
- color: _textMuted,
3023
- fontSize: 11.5,
3024
- height: 1.35,
3025
- ),
2635
+ Text(
2636
+ assistantUi.transcriptPreview.isEmpty
2637
+ ? assistantUi.idleHint
2638
+ : assistantUi.transcriptPreview,
2639
+ maxLines: 1,
2640
+ overflow: TextOverflow.ellipsis,
2641
+ style: TextStyle(
2642
+ color: _textMuted,
2643
+ fontSize: 11.5,
2644
+ height: 1.35,
3026
2645
  ),
2646
+ ),
3027
2647
  ],
3028
2648
  ),
3029
2649
  ),
3030
2650
  const SizedBox(width: 8),
3031
- _VoiceAssistantScreenContextButton(
3032
- controller: controller,
3033
- compact: true,
3034
- ),
3035
- const SizedBox(width: 4),
3036
2651
  FilledButton.icon(
3037
- onPressed: blockedHintVisible
3038
- ? null
3039
- : () {
3040
- unawaited(onPrimaryAction());
3041
- },
2652
+ onPressed: () {
2653
+ unawaited(onPrimaryAction());
2654
+ },
3042
2655
  style: FilledButton.styleFrom(
3043
2656
  visualDensity: VisualDensity.compact,
3044
2657
  padding: const EdgeInsets.symmetric(
@@ -3229,190 +2842,6 @@ class _DesktopAssistantWaveformState extends State<_DesktopAssistantWaveform>
3229
2842
  }
3230
2843
  }
3231
2844
 
3232
- class _DesktopFloatingToolbarSurface extends StatelessWidget {
3233
- const _DesktopFloatingToolbarSurface({
3234
- required this.controller,
3235
- required this.elapsedLabel,
3236
- required this.compactWindow,
3237
- required this.onOpenMainWindow,
3238
- });
3239
-
3240
- final NeoAgentController controller;
3241
- final String elapsedLabel;
3242
- final bool compactWindow;
3243
- final Future<void> Function()? onOpenMainWindow;
3244
-
3245
- @override
3246
- Widget build(BuildContext context) {
3247
- final runtime = controller.recordingRuntime;
3248
- return Material(
3249
- color: Colors.transparent,
3250
- child: Container(
3251
- constraints: BoxConstraints(
3252
- maxWidth: compactWindow ? double.infinity : 680,
3253
- ),
3254
- margin: compactWindow
3255
- ? EdgeInsets.zero
3256
- : const EdgeInsets.symmetric(horizontal: 16),
3257
- padding: EdgeInsets.symmetric(
3258
- horizontal: compactWindow ? 10 : 14,
3259
- vertical: compactWindow ? 8 : 12,
3260
- ),
3261
- decoration: BoxDecoration(
3262
- gradient: LinearGradient(
3263
- colors: <Color>[
3264
- _bgCard.withValues(alpha: 0.98),
3265
- _bgCard.withValues(alpha: 0.92),
3266
- ],
3267
- ),
3268
- borderRadius: BorderRadius.circular(compactWindow ? 22 : 24),
3269
- border: Border.all(color: _borderLight),
3270
- boxShadow: <BoxShadow>[
3271
- BoxShadow(
3272
- color: Colors.black.withValues(alpha: 0.24),
3273
- blurRadius: 24,
3274
- offset: const Offset(0, 12),
3275
- ),
3276
- ],
3277
- ),
3278
- child: Wrap(
3279
- spacing: 10,
3280
- runSpacing: 10,
3281
- crossAxisAlignment: WrapCrossAlignment.center,
3282
- children: <Widget>[
3283
- if (compactWindow)
3284
- const _BrandLockup(
3285
- logoSize: 34,
3286
- titleFontSize: 16,
3287
- direction: Axis.horizontal,
3288
- spacing: 10,
3289
- alignment: CrossAxisAlignment.start,
3290
- ),
3291
- if (compactWindow)
3292
- DragToMoveArea(
3293
- child: Container(
3294
- padding: const EdgeInsets.symmetric(
3295
- horizontal: 8,
3296
- vertical: 6,
3297
- ),
3298
- decoration: BoxDecoration(
3299
- color: _bgSecondary.withValues(alpha: 0.78),
3300
- borderRadius: BorderRadius.circular(12),
3301
- border: Border.all(color: _borderLight),
3302
- ),
3303
- child: Icon(
3304
- Icons.drag_indicator_rounded,
3305
- size: 14,
3306
- color: _textMuted,
3307
- ),
3308
- ),
3309
- ),
3310
- Container(
3311
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
3312
- decoration: BoxDecoration(
3313
- color: (runtime.paused ? _warning : _danger).withValues(
3314
- alpha: 0.10,
3315
- ),
3316
- borderRadius: BorderRadius.circular(16),
3317
- border: Border.all(
3318
- color: (runtime.paused ? _warning : _danger).withValues(
3319
- alpha: 0.20,
3320
- ),
3321
- ),
3322
- ),
3323
- child: Row(
3324
- mainAxisSize: MainAxisSize.min,
3325
- children: <Widget>[
3326
- Icon(
3327
- runtime.paused
3328
- ? Icons.pause_circle_outline
3329
- : Icons.fiber_manual_record_rounded,
3330
- color: runtime.paused ? _warning : _danger,
3331
- size: 18,
3332
- ),
3333
- const SizedBox(width: 8),
3334
- Text(
3335
- runtime.paused ? 'Paused' : 'Recording',
3336
- style: TextStyle(
3337
- color: runtime.paused ? _warning : _danger,
3338
- fontSize: 12,
3339
- fontWeight: FontWeight.w800,
3340
- ),
3341
- ),
3342
- const SizedBox(width: 10),
3343
- Text(
3344
- elapsedLabel,
3345
- style: TextStyle(
3346
- color: _textPrimary,
3347
- fontSize: 12,
3348
- fontWeight: FontWeight.w700,
3349
- ),
3350
- ),
3351
- ],
3352
- ),
3353
- ),
3354
- _AudioLevelBar(
3355
- label: 'MIC',
3356
- valueDb: runtime.microphoneLevelDb,
3357
- color: _accent,
3358
- compact: true,
3359
- ),
3360
- _AudioLevelBar(
3361
- label: 'SYSTEM',
3362
- valueDb: runtime.systemAudioLevelDb,
3363
- color: _accentAlt,
3364
- compact: true,
3365
- ),
3366
- if (compactWindow && onOpenMainWindow != null)
3367
- IconButton(
3368
- tooltip: 'Open NeoAgent',
3369
- onPressed: onOpenMainWindow,
3370
- style: IconButton.styleFrom(
3371
- backgroundColor: _bgSecondary,
3372
- foregroundColor: _textPrimary,
3373
- ),
3374
- icon: const Icon(Icons.open_in_full_rounded),
3375
- ),
3376
- IconButton(
3377
- tooltip: runtime.paused ? 'Resume recording' : 'Pause recording',
3378
- onPressed: runtime.paused
3379
- ? controller.resumeDesktopRecording
3380
- : controller.pauseDesktopRecording,
3381
- style: IconButton.styleFrom(
3382
- backgroundColor: _bgSecondary,
3383
- foregroundColor: _textPrimary,
3384
- ),
3385
- icon: Icon(
3386
- runtime.paused ? Icons.play_arrow_rounded : Icons.pause_rounded,
3387
- ),
3388
- ),
3389
- IconButton(
3390
- tooltip: 'Stop recording',
3391
- onPressed: controller.isStoppingRecording
3392
- ? null
3393
- : controller.stopRecording,
3394
- style: IconButton.styleFrom(
3395
- backgroundColor: _danger.withValues(alpha: 0.12),
3396
- foregroundColor: _danger,
3397
- ),
3398
- icon: const Icon(Icons.stop_rounded),
3399
- ),
3400
- IconButton(
3401
- tooltip: 'Hide floating bar',
3402
- onPressed: controller.hideDesktopFloatingToolbar,
3403
- style: IconButton.styleFrom(
3404
- backgroundColor: _bgSecondary,
3405
- foregroundColor: _textSecondary,
3406
- ),
3407
- icon: const Icon(Icons.close_rounded),
3408
- ),
3409
- ],
3410
- ),
3411
- ),
3412
- );
3413
- }
3414
- }
3415
-
3416
2845
  String _ensureModelValue(
3417
2846
  String value,
3418
2847
  List<ModelMeta> models, {