neoagent 3.0.1-beta.9 → 3.1.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.
- package/.env.example +2 -10
- package/README.md +12 -3
- package/docs/automation.md +37 -0
- package/docs/benchmarking.md +102 -0
- package/docs/billing.md +34 -8
- package/docs/configuration.md +11 -2
- package/docs/getting-started.md +10 -8
- package/docs/operations.md +1 -1
- package/flutter_app/lib/main.dart +3 -0
- package/flutter_app/lib/main_account_settings.dart +138 -0
- package/flutter_app/lib/main_app_shell.dart +38 -13
- 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_models.dart +142 -0
- package/flutter_app/lib/main_navigation.dart +19 -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 +1378 -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 +3 -3
- package/lib/manager.js +184 -66
- package/package.json +4 -1
- package/server/admin/access.js +12 -7
- package/server/admin/admin.js +436 -10
- package/server/admin/billing.js +158 -35
- package/server/admin/index.html +72 -2
- package/server/admin/users.js +15 -15
- package/server/db/database.js +125 -20
- package/server/http/routes.js +1 -0
- package/server/http/static.js +4 -2
- 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 +91077 -87037
- package/server/routes/account.js +53 -0
- package/server/routes/admin.js +345 -64
- package/server/routes/agents.js +203 -21
- package/server/routes/billing.js +5 -0
- package/server/routes/browser.js +8 -1
- package/server/routes/recordings.js +96 -6
- package/server/routes/screenHistory.js +140 -2
- package/server/routes/timeline.js +43 -0
- package/server/services/account/erasure.js +263 -0
- package/server/services/ai/hooks.js +4 -1
- package/server/services/ai/loop/agent_engine_core.js +8 -1
- package/server/services/ai/loop/blank_recovery.js +36 -0
- package/server/services/ai/loop/conversation_loop.js +166 -33
- package/server/services/ai/messagingFallback.js +22 -0
- package/server/services/ai/rate_limits.js +28 -5
- package/server/services/ai/systemPrompt.js +6 -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 +163 -4
- package/server/services/android/controller.js +6 -2
- package/server/services/billing/plans.js +2 -1
- package/server/services/browser/anti_detection.js +192 -0
- package/server/services/browser/controller.js +180 -54
- package/server/services/desktop/auth.js +3 -0
- package/server/services/desktop/registry.js +50 -2
- package/server/services/integrations/google/calendar.js +22 -14
- package/server/services/manager.js +12 -42
- package/server/services/memory/ingestion_chunking.js +268 -0
- package/server/services/messaging/telnyx.js +9 -8
- package/server/services/recordings/manager.js +60 -27
- 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 +234 -9
- package/server/services/tasks/task_repository.js +13 -0
- package/server/services/timeline/service.js +558 -0
- package/server/services/wearable/gateway.js +1 -1
- package/server/services/websocket.js +21 -3
- package/server/services/desktop/screenRecorder.js +0 -292
- package/server/services/desktop/screen_recorder_support.js +0 -46
|
@@ -350,18 +350,22 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
350
350
|
|
|
351
351
|
void _handleHover(Offset point) {
|
|
352
352
|
if (_isBrowser) {
|
|
353
|
-
unawaited(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
353
|
+
unawaited(
|
|
354
|
+
widget.controller.hoverBrowserPointRuntime(
|
|
355
|
+
x: point.dx.round(),
|
|
356
|
+
y: point.dy.round(),
|
|
357
|
+
),
|
|
358
|
+
);
|
|
357
359
|
} else if (_isDesktop) {
|
|
358
360
|
if (_desktopRequiresSelection) {
|
|
359
361
|
return;
|
|
360
362
|
}
|
|
361
|
-
unawaited(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
363
|
+
unawaited(
|
|
364
|
+
widget.controller.hoverDesktopRuntime(
|
|
365
|
+
x: point.dx.round(),
|
|
366
|
+
y: point.dy.round(),
|
|
367
|
+
),
|
|
368
|
+
);
|
|
365
369
|
}
|
|
366
370
|
}
|
|
367
371
|
|
|
@@ -551,34 +555,95 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
551
555
|
onSelect: _selectSurface,
|
|
552
556
|
),
|
|
553
557
|
] else ...<Widget>[
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
558
|
+
if (_isBrowser && prefersExtension) ...<Widget>[
|
|
559
|
+
const SizedBox(height: 14),
|
|
560
|
+
if (_browserExtensionDevices.isNotEmpty) ...<Widget>[
|
|
561
|
+
DropdownButtonFormField<String>(
|
|
562
|
+
isExpanded: true,
|
|
563
|
+
initialValue: selectedBrowserExtension?['tokenId']
|
|
564
|
+
?.toString(),
|
|
565
|
+
decoration: const InputDecoration(
|
|
566
|
+
labelText: 'Chrome extension device',
|
|
567
|
+
prefixIcon: Icon(Icons.extension_outlined),
|
|
568
|
+
),
|
|
569
|
+
hint: const Text('Select a paired extension'),
|
|
570
|
+
items: _browserExtensionDevices.map((device) {
|
|
571
|
+
final tokenId =
|
|
572
|
+
device['tokenId']?.toString() ?? '';
|
|
573
|
+
final label =
|
|
574
|
+
device['name']
|
|
575
|
+
?.toString()
|
|
576
|
+
.trim()
|
|
577
|
+
.isNotEmpty ==
|
|
578
|
+
true
|
|
579
|
+
? device['name'].toString()
|
|
580
|
+
: tokenId;
|
|
581
|
+
final state =
|
|
582
|
+
device['online'] == true ||
|
|
583
|
+
device['connected'] == true
|
|
584
|
+
? 'online'
|
|
585
|
+
: 'offline';
|
|
586
|
+
return DropdownMenuItem<String>(
|
|
587
|
+
value: tokenId,
|
|
588
|
+
child: Text(
|
|
589
|
+
'$label · $state',
|
|
590
|
+
maxLines: 1,
|
|
591
|
+
overflow: TextOverflow.ellipsis,
|
|
592
|
+
softWrap: false,
|
|
593
|
+
),
|
|
594
|
+
);
|
|
595
|
+
}).toList(),
|
|
596
|
+
onChanged: _isCurrentSurfaceBusy
|
|
597
|
+
? null
|
|
598
|
+
: (value) {
|
|
599
|
+
if (value == null || value.isEmpty) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
unawaited(
|
|
603
|
+
controller.selectBrowserExtensionRuntime(
|
|
604
|
+
value,
|
|
605
|
+
),
|
|
606
|
+
);
|
|
607
|
+
},
|
|
608
|
+
),
|
|
609
|
+
const SizedBox(height: 10),
|
|
610
|
+
],
|
|
611
|
+
_ExtensionStatusBar(
|
|
612
|
+
connected: extensionConnected,
|
|
613
|
+
onDownload: controller.downloadBrowserExtension,
|
|
614
|
+
onRefresh: controller.refreshBrowserExtensionStatus,
|
|
615
|
+
),
|
|
616
|
+
],
|
|
617
|
+
if (_isDesktop) ...<Widget>[
|
|
618
|
+
const SizedBox(height: 14),
|
|
557
619
|
DropdownButtonFormField<String>(
|
|
558
620
|
isExpanded: true,
|
|
559
|
-
initialValue:
|
|
621
|
+
initialValue: selectedDesktopDevice?['deviceId']
|
|
560
622
|
?.toString(),
|
|
561
623
|
decoration: const InputDecoration(
|
|
562
|
-
labelText: '
|
|
563
|
-
prefixIcon: Icon(Icons.
|
|
624
|
+
labelText: 'Desktop device',
|
|
625
|
+
prefixIcon: Icon(Icons.computer_outlined),
|
|
564
626
|
),
|
|
565
|
-
hint: const Text('Select a
|
|
566
|
-
items:
|
|
567
|
-
final
|
|
627
|
+
hint: const Text('Select a companion desktop'),
|
|
628
|
+
items: controller.desktopDevices.map((device) {
|
|
629
|
+
final deviceId =
|
|
630
|
+
device['deviceId']?.toString() ?? '';
|
|
568
631
|
final label =
|
|
569
|
-
device['
|
|
632
|
+
device['label']?.toString().trim().isNotEmpty ==
|
|
570
633
|
true
|
|
571
|
-
? device['
|
|
572
|
-
:
|
|
573
|
-
final
|
|
574
|
-
device['
|
|
575
|
-
|
|
576
|
-
? '
|
|
634
|
+
? device['label'].toString()
|
|
635
|
+
: (device['hostname']?.toString() ?? deviceId);
|
|
636
|
+
final os =
|
|
637
|
+
device['platform']?.toString() ?? 'desktop';
|
|
638
|
+
final state = device['online'] == true
|
|
639
|
+
? (device['paused'] == true
|
|
640
|
+
? 'paused'
|
|
641
|
+
: 'online')
|
|
577
642
|
: 'offline';
|
|
578
643
|
return DropdownMenuItem<String>(
|
|
579
|
-
value:
|
|
644
|
+
value: deviceId,
|
|
580
645
|
child: Text(
|
|
581
|
-
'$label · $state',
|
|
646
|
+
'$label · $os · $state',
|
|
582
647
|
maxLines: 1,
|
|
583
648
|
overflow: TextOverflow.ellipsis,
|
|
584
649
|
softWrap: false,
|
|
@@ -592,193 +657,216 @@ class _DevicesPanelState extends State<DevicesPanel> {
|
|
|
592
657
|
return;
|
|
593
658
|
}
|
|
594
659
|
unawaited(
|
|
595
|
-
controller.
|
|
660
|
+
controller.selectDesktopDeviceRuntime(
|
|
596
661
|
value,
|
|
597
662
|
),
|
|
598
663
|
);
|
|
599
664
|
},
|
|
600
665
|
),
|
|
601
666
|
const SizedBox(height: 10),
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
667
|
+
Wrap(
|
|
668
|
+
spacing: 10,
|
|
669
|
+
runSpacing: 10,
|
|
670
|
+
children: <Widget>[
|
|
671
|
+
_DotStatus(
|
|
672
|
+
label: desktopDeviceOnline
|
|
673
|
+
? 'Companion live'
|
|
674
|
+
: controller.desktopCompanionConnected
|
|
675
|
+
? 'Companion live'
|
|
676
|
+
: controller.desktopCompanionConnecting
|
|
677
|
+
? 'Connecting'
|
|
678
|
+
: 'Companion idle',
|
|
679
|
+
color: desktopDeviceOnline
|
|
680
|
+
? _success
|
|
681
|
+
: controller.desktopCompanionConnected
|
|
682
|
+
? _success
|
|
683
|
+
: controller.desktopCompanionConnecting
|
|
684
|
+
? _accent
|
|
685
|
+
: _warning,
|
|
686
|
+
),
|
|
687
|
+
if (selectedDesktopDevice != null)
|
|
688
|
+
_DotStatus(
|
|
689
|
+
label: selectedDesktopDevice['paused'] == true
|
|
690
|
+
? 'Paused'
|
|
691
|
+
: (selectedDesktopDevice['online'] == true
|
|
692
|
+
? 'Ready'
|
|
693
|
+
: 'Offline'),
|
|
694
|
+
color: selectedDesktopDevice['paused'] == true
|
|
695
|
+
? _warning
|
|
696
|
+
: (selectedDesktopDevice['online'] == true
|
|
697
|
+
? _success
|
|
698
|
+
: _textMuted),
|
|
699
|
+
),
|
|
700
|
+
if (selectedDesktopDevice != null)
|
|
701
|
+
_DotStatus(
|
|
702
|
+
label:
|
|
703
|
+
selectedDesktopDevice['passiveHistoryEnabled'] ==
|
|
704
|
+
true
|
|
705
|
+
? 'History on'
|
|
706
|
+
: 'History off',
|
|
707
|
+
color:
|
|
708
|
+
selectedDesktopDevice['passiveHistoryEnabled'] ==
|
|
709
|
+
true
|
|
710
|
+
? _accent
|
|
711
|
+
: _textMuted,
|
|
712
|
+
),
|
|
713
|
+
],
|
|
618
714
|
),
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
softWrap: false,
|
|
715
|
+
if (selectedDesktopDevice != null &&
|
|
716
|
+
(selectedDesktopDevice['passiveHistoryLastUploadedAt']
|
|
717
|
+
?.toString()
|
|
718
|
+
.trim()
|
|
719
|
+
.isNotEmpty ==
|
|
720
|
+
true ||
|
|
721
|
+
selectedDesktopDevice['passiveHistoryLastError']
|
|
722
|
+
?.toString()
|
|
723
|
+
.trim()
|
|
724
|
+
.isNotEmpty ==
|
|
725
|
+
true)) ...<Widget>[
|
|
726
|
+
const SizedBox(height: 10),
|
|
727
|
+
Container(
|
|
728
|
+
width: double.infinity,
|
|
729
|
+
padding: const EdgeInsets.all(12),
|
|
730
|
+
decoration: BoxDecoration(
|
|
731
|
+
color: _bgSecondary.withValues(alpha: 0.7),
|
|
732
|
+
borderRadius: BorderRadius.circular(14),
|
|
733
|
+
border: Border.all(color: _borderLight),
|
|
639
734
|
),
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
: 'Companion idle',
|
|
666
|
-
color: desktopDeviceOnline
|
|
667
|
-
? _success
|
|
668
|
-
: controller.desktopCompanionConnected
|
|
669
|
-
? _success
|
|
670
|
-
: controller.desktopCompanionConnecting
|
|
671
|
-
? _accent
|
|
672
|
-
: _warning,
|
|
673
|
-
),
|
|
674
|
-
if (selectedDesktopDevice != null)
|
|
675
|
-
_DotStatus(
|
|
676
|
-
label: selectedDesktopDevice['paused'] == true
|
|
677
|
-
? 'Paused'
|
|
678
|
-
: (selectedDesktopDevice['online'] == true
|
|
679
|
-
? 'Ready'
|
|
680
|
-
: 'Offline'),
|
|
681
|
-
color: selectedDesktopDevice['paused'] == true
|
|
682
|
-
? _warning
|
|
683
|
-
: (selectedDesktopDevice['online'] == true
|
|
684
|
-
? _success
|
|
685
|
-
: _textMuted),
|
|
735
|
+
child: Column(
|
|
736
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
737
|
+
children: <Widget>[
|
|
738
|
+
if (selectedDesktopDevice['passiveHistoryLastUploadedAt']
|
|
739
|
+
?.toString()
|
|
740
|
+
.trim()
|
|
741
|
+
.isNotEmpty ==
|
|
742
|
+
true)
|
|
743
|
+
Text(
|
|
744
|
+
'Last history upload: ${selectedDesktopDevice['passiveHistoryLastUploadedAt']}',
|
|
745
|
+
style: TextStyle(color: _textSecondary),
|
|
746
|
+
),
|
|
747
|
+
if (selectedDesktopDevice['passiveHistoryLastError']
|
|
748
|
+
?.toString()
|
|
749
|
+
.trim()
|
|
750
|
+
.isNotEmpty ==
|
|
751
|
+
true)
|
|
752
|
+
Padding(
|
|
753
|
+
padding: const EdgeInsets.only(top: 6),
|
|
754
|
+
child: Text(
|
|
755
|
+
'Last history error: ${selectedDesktopDevice['passiveHistoryLastError']}',
|
|
756
|
+
style: TextStyle(color: _danger),
|
|
757
|
+
),
|
|
758
|
+
),
|
|
759
|
+
],
|
|
686
760
|
),
|
|
761
|
+
),
|
|
687
762
|
],
|
|
763
|
+
],
|
|
764
|
+
const SizedBox(height: 16),
|
|
765
|
+
_DeviceLaunchBar(
|
|
766
|
+
surface: _surface,
|
|
767
|
+
controller: _isBrowser
|
|
768
|
+
? _browserUrlController
|
|
769
|
+
: (_isDesktop
|
|
770
|
+
? _desktopLaunchController
|
|
771
|
+
: _androidLaunchController),
|
|
772
|
+
active: _isBrowser
|
|
773
|
+
? browserStatus['launched'] == true
|
|
774
|
+
: (_isDesktop
|
|
775
|
+
? _desktopOnline
|
|
776
|
+
: _androidOnline || _androidStarting),
|
|
777
|
+
starting:
|
|
778
|
+
!_isBrowser && !_isDesktop && _androidStarting,
|
|
779
|
+
busy: _isCurrentSurfaceBusy,
|
|
780
|
+
onSubmit: _openPrimary,
|
|
781
|
+
onSleep: _sleepPrimary,
|
|
688
782
|
),
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
? 'browser'
|
|
722
|
-
: (_isDesktop && _desktopOnline
|
|
723
|
-
? (widget.controller.selectedDesktopDeviceId ??
|
|
724
|
-
(_onlineDesktopDevices.isNotEmpty ? _onlineDesktopDevices.first['deviceId']?.toString() : null))
|
|
725
|
-
: (!_isBrowser && !_isDesktop && _androidOnline
|
|
726
|
-
? _androidDeviceId
|
|
727
|
-
: null)),
|
|
728
|
-
busy: _isCurrentSurfaceBusy,
|
|
729
|
-
wakingUp: !_isBrowser && !_isDesktop && _androidStarting,
|
|
730
|
-
enabled: _isBrowser || _isDesktop || _androidOnline,
|
|
731
|
-
connectRequired: _isBrowser
|
|
732
|
-
? _extensionPreferredButOffline
|
|
733
|
-
: _desktopRequiresSelection,
|
|
734
|
-
onTapPoint: _handleTap,
|
|
735
|
-
onSwipe: _handleSwipe,
|
|
736
|
-
onHover: _handleHover,
|
|
737
|
-
onWakeRequested: _openPrimary,
|
|
738
|
-
),
|
|
739
|
-
if (!_isBrowser && !_isDesktop) ...<Widget>[
|
|
740
|
-
const SizedBox(height: 12),
|
|
741
|
-
_AndroidNavDock(
|
|
783
|
+
const SizedBox(height: 18),
|
|
784
|
+
_InteractiveSurfacePreview(
|
|
785
|
+
surface: _surface,
|
|
786
|
+
controller: controller,
|
|
787
|
+
screenshotPath: _activeScreenshotPath,
|
|
788
|
+
streamPlatform:
|
|
789
|
+
_isBrowser && browserStatus['launched'] == true
|
|
790
|
+
? 'browser'
|
|
791
|
+
: (_isDesktop && _desktopOnline
|
|
792
|
+
? 'desktop'
|
|
793
|
+
: (!_isBrowser &&
|
|
794
|
+
!_isDesktop &&
|
|
795
|
+
_androidOnline
|
|
796
|
+
? 'android'
|
|
797
|
+
: null)),
|
|
798
|
+
streamDeviceId:
|
|
799
|
+
_isBrowser && browserStatus['launched'] == true
|
|
800
|
+
? 'browser'
|
|
801
|
+
: (_isDesktop && _desktopOnline
|
|
802
|
+
? (widget
|
|
803
|
+
.controller
|
|
804
|
+
.selectedDesktopDeviceId ??
|
|
805
|
+
(_onlineDesktopDevices.isNotEmpty
|
|
806
|
+
? _onlineDesktopDevices
|
|
807
|
+
.first['deviceId']
|
|
808
|
+
?.toString()
|
|
809
|
+
: null))
|
|
810
|
+
: (!_isBrowser &&
|
|
811
|
+
!_isDesktop &&
|
|
812
|
+
_androidOnline
|
|
813
|
+
? _androidDeviceId
|
|
814
|
+
: null)),
|
|
742
815
|
busy: _isCurrentSurfaceBusy,
|
|
743
|
-
|
|
744
|
-
|
|
816
|
+
wakingUp:
|
|
817
|
+
!_isBrowser && !_isDesktop && _androidStarting,
|
|
818
|
+
enabled: _isBrowser || _isDesktop || _androidOnline,
|
|
819
|
+
connectRequired: _isBrowser
|
|
820
|
+
? _extensionPreferredButOffline
|
|
821
|
+
: _desktopRequiresSelection,
|
|
822
|
+
onTapPoint: _handleTap,
|
|
823
|
+
onSwipe: _handleSwipe,
|
|
824
|
+
onHover: _handleHover,
|
|
825
|
+
onWakeRequested: _openPrimary,
|
|
745
826
|
),
|
|
746
|
-
if (
|
|
827
|
+
if (!_isBrowser && !_isDesktop) ...<Widget>[
|
|
747
828
|
const SizedBox(height: 12),
|
|
748
|
-
|
|
749
|
-
enabled: _androidOnline,
|
|
829
|
+
_AndroidNavDock(
|
|
750
830
|
busy: _isCurrentSurfaceBusy,
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
831
|
+
androidOnline: _androidOnline,
|
|
832
|
+
onAction: _runQuickAction,
|
|
833
|
+
),
|
|
834
|
+
if (kIsWeb) ...<Widget>[
|
|
835
|
+
const SizedBox(height: 12),
|
|
836
|
+
_AndroidActionsBox(
|
|
837
|
+
enabled: _androidOnline,
|
|
838
|
+
busy: _isCurrentSurfaceBusy,
|
|
839
|
+
onInstall: ({required filename, required bytes}) {
|
|
840
|
+
return controller.installAndroidApkRuntime(
|
|
841
|
+
filename: filename,
|
|
842
|
+
bytes: bytes,
|
|
843
|
+
);
|
|
844
|
+
},
|
|
845
|
+
),
|
|
846
|
+
],
|
|
847
|
+
],
|
|
848
|
+
const SizedBox(height: 18),
|
|
849
|
+
_DeviceTypeDock(
|
|
850
|
+
controller: _textEntryController,
|
|
851
|
+
busy: _isCurrentSurfaceBusy,
|
|
852
|
+
surface: _surface,
|
|
853
|
+
onSubmit: _sendText,
|
|
854
|
+
),
|
|
855
|
+
if (_isBrowser || _isDesktop) ...<Widget>[
|
|
856
|
+
const SizedBox(height: 14),
|
|
857
|
+
_DeviceQuickActions(
|
|
858
|
+
surface: _surface,
|
|
859
|
+
androidOnline: _androidOnline,
|
|
860
|
+
busy: _isCurrentSurfaceBusy,
|
|
861
|
+
onAction: _runQuickAction,
|
|
757
862
|
),
|
|
758
863
|
],
|
|
759
|
-
],
|
|
760
|
-
const SizedBox(height: 18),
|
|
761
|
-
_DeviceTypeDock(
|
|
762
|
-
controller: _textEntryController,
|
|
763
|
-
busy: _isCurrentSurfaceBusy,
|
|
764
|
-
surface: _surface,
|
|
765
|
-
onSubmit: _sendText,
|
|
766
|
-
),
|
|
767
|
-
if (_isBrowser || _isDesktop) ...<Widget>[
|
|
768
864
|
const SizedBox(height: 14),
|
|
769
|
-
|
|
865
|
+
_SurfaceSwitcher(
|
|
770
866
|
surface: _surface,
|
|
771
|
-
|
|
772
|
-
busy: _isCurrentSurfaceBusy,
|
|
773
|
-
onAction: _runQuickAction,
|
|
867
|
+
onSelect: _selectSurface,
|
|
774
868
|
),
|
|
775
869
|
],
|
|
776
|
-
const SizedBox(height: 14),
|
|
777
|
-
_SurfaceSwitcher(
|
|
778
|
-
surface: _surface,
|
|
779
|
-
onSelect: _selectSurface,
|
|
780
|
-
),
|
|
781
|
-
],
|
|
782
870
|
],
|
|
783
871
|
),
|
|
784
872
|
),
|
|
@@ -950,7 +1038,7 @@ class _DeviceSurfaceHeader extends StatelessWidget {
|
|
|
950
1038
|
? 'Live'
|
|
951
1039
|
: 'Offline'))
|
|
952
1040
|
: surface == _DeviceSurface.files
|
|
953
|
-
? '
|
|
1041
|
+
? ''
|
|
954
1042
|
: (androidOnline
|
|
955
1043
|
? 'Live'
|
|
956
1044
|
: androidStarting
|
|
@@ -1336,7 +1424,11 @@ class _MutedBadge extends StatelessWidget {
|
|
|
1336
1424
|
color: Colors.black54,
|
|
1337
1425
|
borderRadius: BorderRadius.circular(8),
|
|
1338
1426
|
),
|
|
1339
|
-
child: const Icon(
|
|
1427
|
+
child: const Icon(
|
|
1428
|
+
Icons.volume_off_rounded,
|
|
1429
|
+
size: 11,
|
|
1430
|
+
color: Colors.white,
|
|
1431
|
+
),
|
|
1340
1432
|
);
|
|
1341
1433
|
}
|
|
1342
1434
|
}
|
|
@@ -1738,10 +1830,7 @@ class _InteractiveSurfacePreviewState
|
|
|
1738
1830
|
final surfaceHeightCap = widget.surface == _DeviceSurface.android
|
|
1739
1831
|
? 640.0
|
|
1740
1832
|
: 560.0;
|
|
1741
|
-
final previewMaxHeight = math.min(
|
|
1742
|
-
surfaceHeightCap,
|
|
1743
|
-
viewportHeight * 0.48,
|
|
1744
|
-
);
|
|
1833
|
+
final previewMaxHeight = math.min(surfaceHeightCap, viewportHeight * 0.48);
|
|
1745
1834
|
final aspectRatio = switch (widget.surface) {
|
|
1746
1835
|
_DeviceSurface.browser => 16 / 10,
|
|
1747
1836
|
_DeviceSurface.android => 10 / 16,
|
|
@@ -1818,10 +1907,7 @@ class _InteractiveSurfacePreviewState
|
|
|
1818
1907
|
const Positioned(
|
|
1819
1908
|
top: 8,
|
|
1820
1909
|
right: 8,
|
|
1821
|
-
child: Opacity(
|
|
1822
|
-
opacity: 0.45,
|
|
1823
|
-
child: _MutedBadge(),
|
|
1824
|
-
),
|
|
1910
|
+
child: Opacity(opacity: 0.45, child: _MutedBadge()),
|
|
1825
1911
|
),
|
|
1826
1912
|
Positioned(
|
|
1827
1913
|
left: 12,
|
|
@@ -2389,10 +2475,8 @@ class _WorkspaceExplorerState extends State<_WorkspaceExplorer> {
|
|
|
2389
2475
|
: ListView.separated(
|
|
2390
2476
|
shrinkWrap: true,
|
|
2391
2477
|
itemCount: entries.length,
|
|
2392
|
-
separatorBuilder: (_, __) =>
|
|
2393
|
-
|
|
2394
|
-
color: _borderLight,
|
|
2395
|
-
),
|
|
2478
|
+
separatorBuilder: (_, __) =>
|
|
2479
|
+
Divider(height: 1, color: _borderLight),
|
|
2396
2480
|
itemBuilder: (context, index) {
|
|
2397
2481
|
final entry = entries[index];
|
|
2398
2482
|
final type = entry['type']?.toString() ?? 'file';
|
|
@@ -2495,7 +2579,9 @@ class _WorkspaceExplorerState extends State<_WorkspaceExplorer> {
|
|
|
2495
2579
|
}
|
|
2496
2580
|
|
|
2497
2581
|
String _formatWorkspaceBytes(Object? value) {
|
|
2498
|
-
final bytes = value is num
|
|
2582
|
+
final bytes = value is num
|
|
2583
|
+
? value.toDouble()
|
|
2584
|
+
: double.tryParse('$value') ?? 0;
|
|
2499
2585
|
if (bytes >= 1024 * 1024) {
|
|
2500
2586
|
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
|
2501
2587
|
}
|