neoagent 2.4.1-beta.13 → 2.4.1-beta.15
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/extensions/chrome-browser/background.mjs +26 -0
- package/extensions/chrome-browser/manifest.json +3 -3
- package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
- package/flutter_app/lib/main_chat.dart +276 -83
- package/flutter_app/lib/main_controller.dart +12 -0
- package/flutter_app/lib/main_devices.dart +39 -0
- package/flutter_app/lib/main_integrations.dart +3 -1
- package/flutter_app/lib/main_unified.dart +29 -1
- package/flutter_app/lib/src/backend_client.dart +11 -0
- package/flutter_app/lib/src/desktop_companion_io.dart +23 -0
- package/flutter_app/lib/src/stream_renderer.dart +42 -3
- package/package.json +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 +56334 -56015
- package/server/routes/voice_assistant.js +36 -1
- package/server/services/browser/extension/registry.js +46 -5
- package/server/services/desktop/registry.js +104 -1
- package/server/services/streaming/android-stream.js +39 -1
- package/server/utils/version.js +29 -19
- package/server/utils/version.test.js +39 -0
|
@@ -129,6 +129,7 @@ class RunsAndLogsPanel extends StatefulWidget {
|
|
|
129
129
|
class _RunsAndLogsPanelState extends State<RunsAndLogsPanel>
|
|
130
130
|
with SingleTickerProviderStateMixin {
|
|
131
131
|
late final TabController _tabController;
|
|
132
|
+
bool _syncingFromController = false;
|
|
132
133
|
|
|
133
134
|
@override
|
|
134
135
|
void initState() {
|
|
@@ -137,7 +138,7 @@ class _RunsAndLogsPanelState extends State<RunsAndLogsPanel>
|
|
|
137
138
|
length: _RunsPageTab.values.length,
|
|
138
139
|
vsync: this,
|
|
139
140
|
initialIndex: _tabForSection(widget.controller.selectedSection).index,
|
|
140
|
-
);
|
|
141
|
+
)..addListener(_handleTabChanged);
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
@override
|
|
@@ -149,17 +150,34 @@ class _RunsAndLogsPanelState extends State<RunsAndLogsPanel>
|
|
|
149
150
|
selectedSection == AppSection.logs)) {
|
|
150
151
|
final targetIndex = _tabForSection(selectedSection).index;
|
|
151
152
|
if (_tabController.index != targetIndex) {
|
|
153
|
+
_syncingFromController = true;
|
|
152
154
|
_tabController.index = targetIndex;
|
|
155
|
+
_syncingFromController = false;
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
@override
|
|
158
161
|
void dispose() {
|
|
162
|
+
_tabController.removeListener(_handleTabChanged);
|
|
159
163
|
_tabController.dispose();
|
|
160
164
|
super.dispose();
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
void _handleTabChanged() {
|
|
168
|
+
if (_syncingFromController || _tabController.indexIsChanging) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
_selectSectionForTabIndex(_tabController.index);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
void _selectSectionForTabIndex(int index) {
|
|
175
|
+
final section = _sectionForTab(_RunsPageTab.values[index]);
|
|
176
|
+
if (widget.controller.selectedSection != section) {
|
|
177
|
+
widget.controller.setSelectedSection(section);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
163
181
|
_RunsPageTab _tabForSection(AppSection section) {
|
|
164
182
|
switch (section) {
|
|
165
183
|
case AppSection.logs:
|
|
@@ -169,6 +187,15 @@ class _RunsAndLogsPanelState extends State<RunsAndLogsPanel>
|
|
|
169
187
|
}
|
|
170
188
|
}
|
|
171
189
|
|
|
190
|
+
AppSection _sectionForTab(_RunsPageTab tab) {
|
|
191
|
+
switch (tab) {
|
|
192
|
+
case _RunsPageTab.logs:
|
|
193
|
+
return AppSection.logs;
|
|
194
|
+
case _RunsPageTab.runs:
|
|
195
|
+
return AppSection.runs;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
172
199
|
@override
|
|
173
200
|
Widget build(BuildContext context) {
|
|
174
201
|
final controller = widget.controller;
|
|
@@ -193,6 +220,7 @@ class _RunsAndLogsPanelState extends State<RunsAndLogsPanel>
|
|
|
193
220
|
dividerColor: _border,
|
|
194
221
|
indicatorSize: TabBarIndicatorSize.tab,
|
|
195
222
|
labelStyle: const TextStyle(fontWeight: FontWeight.w700),
|
|
223
|
+
onTap: _selectSectionForTabIndex,
|
|
196
224
|
tabs: <Widget>[
|
|
197
225
|
Tab(text: 'Runs (${controller.recentRuns.length})'),
|
|
198
226
|
Tab(text: 'Logs (${controller.logs.length})'),
|
|
@@ -1644,6 +1644,17 @@ class BackendClient {
|
|
|
1644
1644
|
await deleteMap(baseUrl, '/api/recordings/$sessionId');
|
|
1645
1645
|
}
|
|
1646
1646
|
|
|
1647
|
+
Future<Map<String, dynamic>> transcribeAudio(
|
|
1648
|
+
String baseUrl, {
|
|
1649
|
+
required String audioBase64,
|
|
1650
|
+
String mimeType = 'audio/pcm;rate=16000;channels=1',
|
|
1651
|
+
}) {
|
|
1652
|
+
return postMap(baseUrl, '/api/voice-assistant/transcribe', <String, dynamic>{
|
|
1653
|
+
'audioBase64': audioBase64,
|
|
1654
|
+
'mimeType': mimeType,
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1647
1658
|
Future<Map<String, dynamic>> runVoiceAssistantTurn(
|
|
1648
1659
|
String baseUrl, {
|
|
1649
1660
|
required String sessionId,
|
|
@@ -25,6 +25,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
25
25
|
final DesktopCompanionActions _actions;
|
|
26
26
|
WebSocket? _socket;
|
|
27
27
|
Timer? _reconnectTimer;
|
|
28
|
+
Timer? _connectionWatchdogTimer;
|
|
28
29
|
Timer? _streamTimer;
|
|
29
30
|
bool _streamCaptureInFlight = false;
|
|
30
31
|
// Set true while a click / drag / scroll / typeText / pressKey command is
|
|
@@ -94,6 +95,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
94
95
|
await disconnect();
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
98
|
+
_ensureConnectionWatchdog();
|
|
97
99
|
await _ensureConnected();
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -138,6 +140,8 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
138
140
|
Future<void> disconnect() async {
|
|
139
141
|
_reconnectTimer?.cancel();
|
|
140
142
|
_reconnectTimer = null;
|
|
143
|
+
_connectionWatchdogTimer?.cancel();
|
|
144
|
+
_connectionWatchdogTimer = null;
|
|
141
145
|
_stopStreaming();
|
|
142
146
|
_connecting = false;
|
|
143
147
|
_connected = false;
|
|
@@ -224,6 +228,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
224
228
|
uri.toString(),
|
|
225
229
|
headers: <String, Object>{'Cookie': _sessionCookie},
|
|
226
230
|
);
|
|
231
|
+
socket.pingInterval = const Duration(seconds: 25);
|
|
227
232
|
_socket = socket;
|
|
228
233
|
socket.listen(
|
|
229
234
|
_handleMessage,
|
|
@@ -470,6 +475,8 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
470
475
|
void dispose() {
|
|
471
476
|
_reconnectTimer?.cancel();
|
|
472
477
|
_reconnectTimer = null;
|
|
478
|
+
_connectionWatchdogTimer?.cancel();
|
|
479
|
+
_connectionWatchdogTimer = null;
|
|
473
480
|
_stopStreaming();
|
|
474
481
|
_connecting = false;
|
|
475
482
|
_connected = false;
|
|
@@ -486,12 +493,28 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
486
493
|
|
|
487
494
|
void _scheduleReconnect() {
|
|
488
495
|
if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return;
|
|
496
|
+
_ensureConnectionWatchdog();
|
|
489
497
|
_reconnectTimer?.cancel();
|
|
490
498
|
_reconnectTimer = Timer(const Duration(seconds: 5), () {
|
|
491
499
|
unawaited(_ensureConnected());
|
|
492
500
|
});
|
|
493
501
|
}
|
|
494
502
|
|
|
503
|
+
void _ensureConnectionWatchdog() {
|
|
504
|
+
if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return;
|
|
505
|
+
if (_connectionWatchdogTimer != null) return;
|
|
506
|
+
_connectionWatchdogTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
|
507
|
+
if (!_enabled || !_authenticated || _sessionCookie.isEmpty) {
|
|
508
|
+
_connectionWatchdogTimer?.cancel();
|
|
509
|
+
_connectionWatchdogTimer = null;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (!_connected && !_connecting) {
|
|
513
|
+
unawaited(_ensureConnected());
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
495
518
|
Future<void> _sendEvent(String event, Map<String, Object?> payload) async {
|
|
496
519
|
final socket = _socket;
|
|
497
520
|
if (socket == null || !_connected) return;
|
|
@@ -15,6 +15,9 @@ class StreamRenderer extends StatefulWidget {
|
|
|
15
15
|
this.onSwipe,
|
|
16
16
|
this.onType,
|
|
17
17
|
this.onHover,
|
|
18
|
+
this.onFirstFrame,
|
|
19
|
+
this.onFrameTimeout,
|
|
20
|
+
this.firstFrameTimeout = const Duration(seconds: 8),
|
|
18
21
|
this.fit = BoxFit.contain,
|
|
19
22
|
this.alignment = Alignment.center,
|
|
20
23
|
});
|
|
@@ -27,6 +30,9 @@ class StreamRenderer extends StatefulWidget {
|
|
|
27
30
|
final void Function(double x1, double y1, double x2, double y2)? onSwipe;
|
|
28
31
|
final void Function(String text)? onType;
|
|
29
32
|
final void Function(double x, double y)? onHover;
|
|
33
|
+
final VoidCallback? onFirstFrame;
|
|
34
|
+
final VoidCallback? onFrameTimeout;
|
|
35
|
+
final Duration firstFrameTimeout;
|
|
30
36
|
final BoxFit fit;
|
|
31
37
|
final Alignment alignment;
|
|
32
38
|
|
|
@@ -43,6 +49,8 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
43
49
|
Offset? _dragEnd;
|
|
44
50
|
|
|
45
51
|
Timer? _hoverThrottleTimer;
|
|
52
|
+
Timer? _firstFrameTimer;
|
|
53
|
+
bool _firstFrameTimedOut = false;
|
|
46
54
|
Offset? _pendingHoverOffset;
|
|
47
55
|
DateTime _lastHoverTime = DateTime.fromMillisecondsSinceEpoch(0);
|
|
48
56
|
|
|
@@ -54,6 +62,7 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
54
62
|
'deviceId': widget.deviceId,
|
|
55
63
|
'platform': widget.platform,
|
|
56
64
|
});
|
|
65
|
+
_startFirstFrameTimer();
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
@override
|
|
@@ -65,7 +74,9 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
65
74
|
return;
|
|
66
75
|
}
|
|
67
76
|
_frameSize = null;
|
|
77
|
+
_frame = null;
|
|
68
78
|
_detachImageListener();
|
|
79
|
+
_firstFrameTimer?.cancel();
|
|
69
80
|
oldWidget.socket.off('stream:frame', _onFrame);
|
|
70
81
|
oldWidget.socket.emit('stream:unsubscribe', <String, Object?>{
|
|
71
82
|
'deviceId': oldWidget.deviceId,
|
|
@@ -76,6 +87,24 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
76
87
|
'deviceId': widget.deviceId,
|
|
77
88
|
'platform': widget.platform,
|
|
78
89
|
});
|
|
90
|
+
_startFirstFrameTimer();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void _startFirstFrameTimer() {
|
|
94
|
+
_firstFrameTimer?.cancel();
|
|
95
|
+
_firstFrameTimedOut = false;
|
|
96
|
+
if (widget.onFrameTimeout == null ||
|
|
97
|
+
widget.firstFrameTimeout <= Duration.zero ||
|
|
98
|
+
_frame != null) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
_firstFrameTimer = Timer(widget.firstFrameTimeout, () {
|
|
102
|
+
if (!mounted || _frame != null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
_firstFrameTimedOut = true;
|
|
106
|
+
widget.onFrameTimeout?.call();
|
|
107
|
+
});
|
|
79
108
|
}
|
|
80
109
|
|
|
81
110
|
void _onFrame(dynamic data) {
|
|
@@ -102,6 +131,14 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
102
131
|
_ => null,
|
|
103
132
|
};
|
|
104
133
|
if (frame == null || frame.isEmpty || !mounted) return;
|
|
134
|
+
final hadFrame = _frame != null;
|
|
135
|
+
if (!hadFrame) {
|
|
136
|
+
_firstFrameTimer?.cancel();
|
|
137
|
+
_firstFrameTimer = null;
|
|
138
|
+
if (!_firstFrameTimedOut) {
|
|
139
|
+
widget.onFirstFrame?.call();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
105
142
|
if (_frameSize == null) {
|
|
106
143
|
_resolveFrameSize(frame);
|
|
107
144
|
}
|
|
@@ -146,7 +183,8 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
146
183
|
return MouseRegion(
|
|
147
184
|
onHover: widget.onHover == null
|
|
148
185
|
? null
|
|
149
|
-
: (event) =>
|
|
186
|
+
: (event) =>
|
|
187
|
+
_handleHoverEvent(event.localPosition, constraints.biggest),
|
|
150
188
|
child: GestureDetector(
|
|
151
189
|
behavior: HitTestBehavior.opaque,
|
|
152
190
|
onTapDown: widget.onTap == null
|
|
@@ -197,7 +235,7 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
197
235
|
final now = DateTime.now();
|
|
198
236
|
final elapsed = now.difference(_lastHoverTime);
|
|
199
237
|
const throttleDuration = Duration(milliseconds: 70);
|
|
200
|
-
|
|
238
|
+
|
|
201
239
|
if (elapsed >= throttleDuration) {
|
|
202
240
|
_sendPendingHover(boxSize);
|
|
203
241
|
} else {
|
|
@@ -213,7 +251,7 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
213
251
|
if (offset == null) return;
|
|
214
252
|
_pendingHoverOffset = null;
|
|
215
253
|
_lastHoverTime = DateTime.now();
|
|
216
|
-
|
|
254
|
+
|
|
217
255
|
final point = _mapToRemote(offset, boxSize);
|
|
218
256
|
if (point != null) {
|
|
219
257
|
widget.onHover?.call(point.dx, point.dy);
|
|
@@ -275,6 +313,7 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
275
313
|
@override
|
|
276
314
|
void dispose() {
|
|
277
315
|
_hoverThrottleTimer?.cancel();
|
|
316
|
+
_firstFrameTimer?.cancel();
|
|
278
317
|
widget.socket.emit('stream:unsubscribe', <String, Object?>{
|
|
279
318
|
'deviceId': widget.deviceId,
|
|
280
319
|
'platform': widget.platform,
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8dab82a7512518b6f2bb17abf16b00fe
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"4c525dac5ebe5971c5708ef73558ed8edcf4a3
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "617137012" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|