neoagent 2.4.1-beta.13 → 2.4.1-beta.14
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/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
- package/flutter_app/lib/main_chat.dart +88 -0
- package/flutter_app/lib/main_controller.dart +12 -0
- package/flutter_app/lib/main_integrations.dart +3 -1
- package/flutter_app/lib/src/backend_client.dart +11 -0
- 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 +48513 -48367
- package/server/routes/voice_assistant.js +36 -1
|
@@ -124,7 +124,8 @@
|
|
|
124
124
|
|
|
125
125
|
<service
|
|
126
126
|
android:name=".auto.NeoAgentCarAppService"
|
|
127
|
-
android:exported="true"
|
|
127
|
+
android:exported="true"
|
|
128
|
+
android:permission="android.car.permission.BIND_CAR_APP_SERVICE">
|
|
128
129
|
<intent-filter>
|
|
129
130
|
<action android:name="androidx.car.app.CarAppService" />
|
|
130
131
|
<category android:name="androidx.car.app.category.IOT" />
|
|
@@ -19,6 +19,10 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
19
19
|
int _lastToolCount = 0;
|
|
20
20
|
String _lastStream = '';
|
|
21
21
|
bool _isSendingChatMessage = false;
|
|
22
|
+
bool _isDictating = false;
|
|
23
|
+
bool _isTranscribing = false;
|
|
24
|
+
LiveVoiceCapture? _dictationCapture;
|
|
25
|
+
final List<Uint8List> _dictationChunks = [];
|
|
22
26
|
|
|
23
27
|
@override
|
|
24
28
|
void initState() {
|
|
@@ -44,9 +48,73 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
44
48
|
widget.controller.removeListener(_consumeQueuedDraft);
|
|
45
49
|
_composerController.dispose();
|
|
46
50
|
_scrollController.dispose();
|
|
51
|
+
_dictationCapture?.dispose();
|
|
47
52
|
super.dispose();
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
Future<void> _startDictation() async {
|
|
56
|
+
if (_isDictating || _isTranscribing) return;
|
|
57
|
+
final capture = LiveVoiceCapture();
|
|
58
|
+
_dictationCapture = capture;
|
|
59
|
+
_dictationChunks.clear();
|
|
60
|
+
try {
|
|
61
|
+
await capture.start(
|
|
62
|
+
onChunk: (chunk) => _dictationChunks.add(chunk),
|
|
63
|
+
sampleRate: 16000,
|
|
64
|
+
channels: 1,
|
|
65
|
+
);
|
|
66
|
+
if (mounted) setState(() => _isDictating = true);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
await capture.dispose();
|
|
69
|
+
_dictationCapture = null;
|
|
70
|
+
if (mounted) {
|
|
71
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
72
|
+
SnackBar(content: Text('Microphone error: $e')),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Future<void> _stopAndTranscribe() async {
|
|
79
|
+
if (!_isDictating) return;
|
|
80
|
+
final capture = _dictationCapture;
|
|
81
|
+
_dictationCapture = null;
|
|
82
|
+
setState(() {
|
|
83
|
+
_isDictating = false;
|
|
84
|
+
_isTranscribing = true;
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
await capture?.stop();
|
|
88
|
+
if (_dictationChunks.isEmpty) return;
|
|
89
|
+
final allBytes = _dictationChunks.fold<List<int>>(
|
|
90
|
+
<int>[],
|
|
91
|
+
(acc, chunk) => acc..addAll(chunk),
|
|
92
|
+
);
|
|
93
|
+
final audioBase64 = base64Encode(Uint8List.fromList(allBytes));
|
|
94
|
+
final transcript = await widget.controller.transcribeDictationAudio(
|
|
95
|
+
audioBase64: audioBase64,
|
|
96
|
+
);
|
|
97
|
+
if (mounted && transcript.isNotEmpty) {
|
|
98
|
+
final current = _composerController.text;
|
|
99
|
+
final separator = current.isNotEmpty && !current.endsWith(' ') ? ' ' : '';
|
|
100
|
+
_composerController.text = '$current$separator$transcript';
|
|
101
|
+
_composerController.selection = TextSelection.collapsed(
|
|
102
|
+
offset: _composerController.text.length,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
if (mounted) {
|
|
107
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
108
|
+
SnackBar(content: Text('Transcription failed: $e')),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
} finally {
|
|
112
|
+
await capture?.dispose();
|
|
113
|
+
_dictationChunks.clear();
|
|
114
|
+
if (mounted) setState(() => _isTranscribing = false);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
50
118
|
void _consumeQueuedDraft() {
|
|
51
119
|
final draft = widget.controller.peekPendingChatDraft();
|
|
52
120
|
final attachments = widget.controller.peekPendingSharedChatAttachments();
|
|
@@ -331,6 +399,26 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
331
399
|
color: _textSecondary,
|
|
332
400
|
),
|
|
333
401
|
const SizedBox(width: 2),
|
|
402
|
+
_isTranscribing
|
|
403
|
+
? const SizedBox(
|
|
404
|
+
width: 40,
|
|
405
|
+
height: 40,
|
|
406
|
+
child: Padding(
|
|
407
|
+
padding: EdgeInsets.all(10),
|
|
408
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
409
|
+
),
|
|
410
|
+
)
|
|
411
|
+
: IconButton(
|
|
412
|
+
tooltip: _isDictating ? 'Stop & transcribe' : 'Dictate',
|
|
413
|
+
onPressed: _isDictating ? _stopAndTranscribe : _startDictation,
|
|
414
|
+
icon: Icon(
|
|
415
|
+
_isDictating
|
|
416
|
+
? Icons.stop_circle_outlined
|
|
417
|
+
: Icons.mic_none_rounded,
|
|
418
|
+
),
|
|
419
|
+
color: _isDictating ? Theme.of(context).colorScheme.error : _textSecondary,
|
|
420
|
+
),
|
|
421
|
+
const SizedBox(width: 2),
|
|
334
422
|
FilledButton(
|
|
335
423
|
onPressed: () => controller.setSelectedSection(
|
|
336
424
|
AppSection.voiceAssistant,
|
|
@@ -4602,6 +4602,18 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
4602
4602
|
}
|
|
4603
4603
|
}
|
|
4604
4604
|
|
|
4605
|
+
Future<String> transcribeDictationAudio({
|
|
4606
|
+
required String audioBase64,
|
|
4607
|
+
String mimeType = 'audio/pcm;rate=16000;channels=1',
|
|
4608
|
+
}) async {
|
|
4609
|
+
final result = await _backendClient.transcribeAudio(
|
|
4610
|
+
backendUrl,
|
|
4611
|
+
audioBase64: audioBase64,
|
|
4612
|
+
mimeType: mimeType,
|
|
4613
|
+
);
|
|
4614
|
+
return result['transcript']?.toString() ?? '';
|
|
4615
|
+
}
|
|
4616
|
+
|
|
4605
4617
|
Future<void> sendMessage(
|
|
4606
4618
|
String task, {
|
|
4607
4619
|
List<SharedChatAttachment> sharedAttachments =
|
|
@@ -188,7 +188,9 @@ class OfficialIntegrationsTab extends StatelessWidget {
|
|
|
188
188
|
!item.env.configured
|
|
189
189
|
? item.env.summary
|
|
190
190
|
: item.hasExpiredAccounts
|
|
191
|
-
?
|
|
191
|
+
? item.id == 'google_workspace'
|
|
192
|
+
? 'One or more accounts expired. Reconnect to restore access. If this keeps happening, your Google Cloud OAuth app may be in Testing mode — publish it to Production in Google Cloud Console to get long-lived tokens.'
|
|
193
|
+
: 'One or more accounts expired. Reconnect the affected account to restore tool access.'
|
|
192
194
|
: !item.supportsMultipleAccounts && item.isConnected
|
|
193
195
|
? 'This integration currently supports one connected account per agent. Re-open setup to replace it.'
|
|
194
196
|
: item.isConnected
|
|
@@ -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,
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
000b8deec67bf984f6cfa4613359729d
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"4c525dac5ebe5971c5708ef73558ed8edcf4a3
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "3177522597" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|