aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMalik Talha <talhamalik727x@gmail.com>2023-10-23 02:00:01 +0500
committerMalik Talha <talhamalik727x@gmail.com>2023-10-26 21:36:57 +0500
commit8417e9daeecbdb3847de401b0fcc6304d246a787 (patch)
treea9dafba232ee7e458358861d1356636c079a12da
parent8d38450e46ff3854ade4005c4132edfb1aabb9b4 (diff)
Add flutter voice assistant app
A flutter based gRPC client Voice Assistant made specifically to communicate with the Voice Agent service. SPEC-4906 Signed-off-by: Malik Talha <talhamalik727x@gmail.com> Change-Id: Ic4a382c1cdb78f1a79f985e3d37ce2fb06c53203
-rw-r--r--.gitignore99
-rw-r--r--.gitreview5
-rw-r--r--.metadata45
-rw-r--r--LICENSE201
-rw-r--r--README.md16
-rw-r--r--analysis_options.yaml10
-rw-r--r--assets/agl_logo.pngbin0 -> 107245 bytes
-rw-r--r--assets/config.json4
-rw-r--r--lib/grpc/generated/voice_agent.pb.dart667
-rw-r--r--lib/grpc/generated/voice_agent.pbenum.dart121
-rw-r--r--lib/grpc/generated/voice_agent.pbgrpc.dart136
-rw-r--r--lib/grpc/generated/voice_agent.pbjson.dart251
-rw-r--r--lib/grpc/voice_agent_client.dart71
-rw-r--r--lib/main.dart77
-rw-r--r--lib/models/app_state.dart8
-rw-r--r--lib/protos/voice_agent.proto83
-rw-r--r--lib/providers/service_status.dart12
-rw-r--r--lib/screens/error_screen.dart62
-rw-r--r--lib/screens/home_screen.dart398
-rw-r--r--lib/utils/app_config.dart26
-rw-r--r--lib/widgets/assistant_mode_choice.dart202
-rw-r--r--lib/widgets/chat_section.dart128
-rw-r--r--lib/widgets/listen_wake_word_section.dart66
-rw-r--r--lib/widgets/nlu_engine_choice.dart200
-rw-r--r--lib/widgets/record_command_button.dart66
-rw-r--r--linux/.gitignore1
-rw-r--r--linux/CMakeLists.txt139
-rw-r--r--linux/flutter/CMakeLists.txt88
-rw-r--r--linux/flutter/generated_plugin_registrant.cc11
-rw-r--r--linux/flutter/generated_plugin_registrant.h15
-rw-r--r--linux/flutter/generated_plugins.cmake23
-rw-r--r--linux/main.cc6
-rw-r--r--linux/my_application.cc104
-rw-r--r--linux/my_application.h18
-rw-r--r--pubspec.lock309
-rw-r--r--pubspec.yaml27
36 files changed, 3695 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0295d78
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,99 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
+
+# Flutter/Dart-specific files
+.dart_tool/
+.flutter-plugins
+.pub/
+build/
+.dart_tool/
+.idea/
+.packages
+
+# Android-specific files
+android/
+*.iml
+local.properties
+.gradle
+/build/
+/captures/
+
+# iOS-specific files
+ios/
+Podfile.lock
+Pods/
+*.xcworkspace
+*.pbxproj
+*.mode1v3
+*.mode2v3
+*.perspectivev3
+
+# Windows-specific files
+windows/
+*.suo
+*.user
+*.sln
+*.vcxproj
+*.filters
+Runner.sln
+Runner.vcxproj
+Runner.vcxproj.filters
+
+# macOS-specific files
+macos/
+macos/Flutter/Flutter.xcodeproj/
+macos/Flutter/Generated.xcconfig/
+macos/Flutter/flutter_assets/
+macos/Flutter/clang.xcconfig
+macos/Flutter/AppFrameworkInfo.plist
+macos/Flutter/flutter_export_environment.sh
+macos/Runner.xcodeproj/
+
+# Web-specific files
+web/
+web/index.html
+web/main.dart.js
+web/packages/
+web/.dart_tool/ \ No newline at end of file
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..01a0a4f
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,5 @@
+[gerrit]
+host=gerrit.automotivelinux.org
+port=29418
+project=apps/flutter-speechrecognition-demo
+defaultbranch=master
diff --git a/.metadata b/.metadata
new file mode 100644
index 0000000..620e877
--- /dev/null
+++ b/.metadata
@@ -0,0 +1,45 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ - platform: android
+ create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ - platform: ios
+ create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ - platform: linux
+ create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ - platform: macos
+ create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ - platform: web
+ create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ - platform: windows
+ create_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+ base_revision: ff5b5b5fa6f35b717667719ddfdb1521d8bdd05a
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..719c157
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2023 Malik Talha, Automotive Grade Linux (AGL) Community
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..09c1f6e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
+# Flutter Voice Assistant
+
+A new Flutter project.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..758a51f
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,10 @@
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ rules:
+ prefer_const_constructors: false
+ prefer_final_fields: false
+ use_key_in_widget_constructors: false
+ prefer_const_literals_to_create_immutables: false
+ prefer_const_constructors_in_immutables: false
+ avoid_print: false \ No newline at end of file
diff --git a/assets/agl_logo.png b/assets/agl_logo.png
new file mode 100644
index 0000000..ffe5d19
--- /dev/null
+++ b/assets/agl_logo.png
Binary files differ
diff --git a/assets/config.json b/assets/config.json
new file mode 100644
index 0000000..6920f9b
--- /dev/null
+++ b/assets/config.json
@@ -0,0 +1,4 @@
+{
+ "grpc_host": "127.0.0.1",
+ "grpc_port": 51053
+} \ No newline at end of file
diff --git a/lib/grpc/generated/voice_agent.pb.dart b/lib/grpc/generated/voice_agent.pb.dart
new file mode 100644
index 0000000..363e93f
--- /dev/null
+++ b/lib/grpc/generated/voice_agent.pb.dart
@@ -0,0 +1,667 @@
+//
+// Generated code. Do not modify.
+// source: voice_agent.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'voice_agent.pbenum.dart';
+
+export 'voice_agent.pbenum.dart';
+
+class Empty extends $pb.GeneratedMessage {
+ factory Empty() => create();
+ Empty._() : super();
+ factory Empty.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory Empty.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'Empty',
+ createEmptyInstance: create)
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ Empty clone() => Empty()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ Empty copyWith(void Function(Empty) updates) =>
+ super.copyWith((message) => updates(message as Empty)) as Empty;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static Empty create() => Empty._();
+ Empty createEmptyInstance() => create();
+ static $pb.PbList<Empty> createRepeated() => $pb.PbList<Empty>();
+ @$core.pragma('dart2js:noInline')
+ static Empty getDefault() =>
+ _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Empty>(create);
+ static Empty? _defaultInstance;
+}
+
+class ServiceStatus extends $pb.GeneratedMessage {
+ factory ServiceStatus({
+ $core.String? version,
+ $core.bool? status,
+ }) {
+ final $result = create();
+ if (version != null) {
+ $result.version = version;
+ }
+ if (status != null) {
+ $result.status = status;
+ }
+ return $result;
+ }
+ ServiceStatus._() : super();
+ factory ServiceStatus.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory ServiceStatus.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'ServiceStatus',
+ createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'version')
+ ..aOB(2, _omitFieldNames ? '' : 'status')
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ ServiceStatus clone() => ServiceStatus()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ ServiceStatus copyWith(void Function(ServiceStatus) updates) =>
+ super.copyWith((message) => updates(message as ServiceStatus))
+ as ServiceStatus;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static ServiceStatus create() => ServiceStatus._();
+ ServiceStatus createEmptyInstance() => create();
+ static $pb.PbList<ServiceStatus> createRepeated() =>
+ $pb.PbList<ServiceStatus>();
+ @$core.pragma('dart2js:noInline')
+ static ServiceStatus getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<ServiceStatus>(create);
+ static ServiceStatus? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get version => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set version($core.String v) {
+ $_setString(0, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasVersion() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearVersion() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.bool get status => $_getBF(1);
+ @$pb.TagNumber(2)
+ set status($core.bool v) {
+ $_setBool(1, v);
+ }
+
+ @$pb.TagNumber(2)
+ $core.bool hasStatus() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearStatus() => clearField(2);
+}
+
+class WakeWordStatus extends $pb.GeneratedMessage {
+ factory WakeWordStatus({
+ $core.bool? status,
+ }) {
+ final $result = create();
+ if (status != null) {
+ $result.status = status;
+ }
+ return $result;
+ }
+ WakeWordStatus._() : super();
+ factory WakeWordStatus.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory WakeWordStatus.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'WakeWordStatus',
+ createEmptyInstance: create)
+ ..aOB(1, _omitFieldNames ? '' : 'status')
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ WakeWordStatus clone() => WakeWordStatus()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ WakeWordStatus copyWith(void Function(WakeWordStatus) updates) =>
+ super.copyWith((message) => updates(message as WakeWordStatus))
+ as WakeWordStatus;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static WakeWordStatus create() => WakeWordStatus._();
+ WakeWordStatus createEmptyInstance() => create();
+ static $pb.PbList<WakeWordStatus> createRepeated() =>
+ $pb.PbList<WakeWordStatus>();
+ @$core.pragma('dart2js:noInline')
+ static WakeWordStatus getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<WakeWordStatus>(create);
+ static WakeWordStatus? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.bool get status => $_getBF(0);
+ @$pb.TagNumber(1)
+ set status($core.bool v) {
+ $_setBool(0, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasStatus() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearStatus() => clearField(1);
+}
+
+class RecognizeControl extends $pb.GeneratedMessage {
+ factory RecognizeControl({
+ RecordAction? action,
+ NLUModel? nluModel,
+ RecordMode? recordMode,
+ $core.String? streamId,
+ }) {
+ final $result = create();
+ if (action != null) {
+ $result.action = action;
+ }
+ if (nluModel != null) {
+ $result.nluModel = nluModel;
+ }
+ if (recordMode != null) {
+ $result.recordMode = recordMode;
+ }
+ if (streamId != null) {
+ $result.streamId = streamId;
+ }
+ return $result;
+ }
+ RecognizeControl._() : super();
+ factory RecognizeControl.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory RecognizeControl.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'RecognizeControl',
+ createEmptyInstance: create)
+ ..e<RecordAction>(1, _omitFieldNames ? '' : 'action', $pb.PbFieldType.OE,
+ defaultOrMaker: RecordAction.START,
+ valueOf: RecordAction.valueOf,
+ enumValues: RecordAction.values)
+ ..e<NLUModel>(2, _omitFieldNames ? '' : 'nluModel', $pb.PbFieldType.OE,
+ defaultOrMaker: NLUModel.SNIPS,
+ valueOf: NLUModel.valueOf,
+ enumValues: NLUModel.values)
+ ..e<RecordMode>(3, _omitFieldNames ? '' : 'recordMode', $pb.PbFieldType.OE,
+ defaultOrMaker: RecordMode.MANUAL,
+ valueOf: RecordMode.valueOf,
+ enumValues: RecordMode.values)
+ ..aOS(4, _omitFieldNames ? '' : 'streamId')
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ RecognizeControl clone() => RecognizeControl()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ RecognizeControl copyWith(void Function(RecognizeControl) updates) =>
+ super.copyWith((message) => updates(message as RecognizeControl))
+ as RecognizeControl;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static RecognizeControl create() => RecognizeControl._();
+ RecognizeControl createEmptyInstance() => create();
+ static $pb.PbList<RecognizeControl> createRepeated() =>
+ $pb.PbList<RecognizeControl>();
+ @$core.pragma('dart2js:noInline')
+ static RecognizeControl getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<RecognizeControl>(create);
+ static RecognizeControl? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ RecordAction get action => $_getN(0);
+ @$pb.TagNumber(1)
+ set action(RecordAction v) {
+ setField(1, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasAction() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearAction() => clearField(1);
+
+ @$pb.TagNumber(2)
+ NLUModel get nluModel => $_getN(1);
+ @$pb.TagNumber(2)
+ set nluModel(NLUModel v) {
+ setField(2, v);
+ }
+
+ @$pb.TagNumber(2)
+ $core.bool hasNluModel() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearNluModel() => clearField(2);
+
+ @$pb.TagNumber(3)
+ RecordMode get recordMode => $_getN(2);
+ @$pb.TagNumber(3)
+ set recordMode(RecordMode v) {
+ setField(3, v);
+ }
+
+ @$pb.TagNumber(3)
+ $core.bool hasRecordMode() => $_has(2);
+ @$pb.TagNumber(3)
+ void clearRecordMode() => clearField(3);
+
+ @$pb.TagNumber(4)
+ $core.String get streamId => $_getSZ(3);
+ @$pb.TagNumber(4)
+ set streamId($core.String v) {
+ $_setString(3, v);
+ }
+
+ @$pb.TagNumber(4)
+ $core.bool hasStreamId() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearStreamId() => clearField(4);
+}
+
+class IntentSlot extends $pb.GeneratedMessage {
+ factory IntentSlot({
+ $core.String? name,
+ $core.String? value,
+ }) {
+ final $result = create();
+ if (name != null) {
+ $result.name = name;
+ }
+ if (value != null) {
+ $result.value = value;
+ }
+ return $result;
+ }
+ IntentSlot._() : super();
+ factory IntentSlot.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory IntentSlot.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'IntentSlot',
+ createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'name')
+ ..aOS(2, _omitFieldNames ? '' : 'value')
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ IntentSlot clone() => IntentSlot()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ IntentSlot copyWith(void Function(IntentSlot) updates) =>
+ super.copyWith((message) => updates(message as IntentSlot)) as IntentSlot;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static IntentSlot create() => IntentSlot._();
+ IntentSlot createEmptyInstance() => create();
+ static $pb.PbList<IntentSlot> createRepeated() => $pb.PbList<IntentSlot>();
+ @$core.pragma('dart2js:noInline')
+ static IntentSlot getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<IntentSlot>(create);
+ static IntentSlot? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get name => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set name($core.String v) {
+ $_setString(0, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasName() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearName() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.String get value => $_getSZ(1);
+ @$pb.TagNumber(2)
+ set value($core.String v) {
+ $_setString(1, v);
+ }
+
+ @$pb.TagNumber(2)
+ $core.bool hasValue() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearValue() => clearField(2);
+}
+
+class RecognizeResult extends $pb.GeneratedMessage {
+ factory RecognizeResult({
+ $core.String? command,
+ $core.String? intent,
+ $core.Iterable<IntentSlot>? intentSlots,
+ $core.String? streamId,
+ RecognizeStatusType? status,
+ }) {
+ final $result = create();
+ if (command != null) {
+ $result.command = command;
+ }
+ if (intent != null) {
+ $result.intent = intent;
+ }
+ if (intentSlots != null) {
+ $result.intentSlots.addAll(intentSlots);
+ }
+ if (streamId != null) {
+ $result.streamId = streamId;
+ }
+ if (status != null) {
+ $result.status = status;
+ }
+ return $result;
+ }
+ RecognizeResult._() : super();
+ factory RecognizeResult.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory RecognizeResult.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'RecognizeResult',
+ createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'command')
+ ..aOS(2, _omitFieldNames ? '' : 'intent')
+ ..pc<IntentSlot>(
+ 3, _omitFieldNames ? '' : 'intentSlots', $pb.PbFieldType.PM,
+ subBuilder: IntentSlot.create)
+ ..aOS(4, _omitFieldNames ? '' : 'streamId')
+ ..e<RecognizeStatusType>(
+ 5, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE,
+ defaultOrMaker: RecognizeStatusType.REC_ERROR,
+ valueOf: RecognizeStatusType.valueOf,
+ enumValues: RecognizeStatusType.values)
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ RecognizeResult clone() => RecognizeResult()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ RecognizeResult copyWith(void Function(RecognizeResult) updates) =>
+ super.copyWith((message) => updates(message as RecognizeResult))
+ as RecognizeResult;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static RecognizeResult create() => RecognizeResult._();
+ RecognizeResult createEmptyInstance() => create();
+ static $pb.PbList<RecognizeResult> createRepeated() =>
+ $pb.PbList<RecognizeResult>();
+ @$core.pragma('dart2js:noInline')
+ static RecognizeResult getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<RecognizeResult>(create);
+ static RecognizeResult? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get command => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set command($core.String v) {
+ $_setString(0, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasCommand() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearCommand() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.String get intent => $_getSZ(1);
+ @$pb.TagNumber(2)
+ set intent($core.String v) {
+ $_setString(1, v);
+ }
+
+ @$pb.TagNumber(2)
+ $core.bool hasIntent() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearIntent() => clearField(2);
+
+ @$pb.TagNumber(3)
+ $core.List<IntentSlot> get intentSlots => $_getList(2);
+
+ @$pb.TagNumber(4)
+ $core.String get streamId => $_getSZ(3);
+ @$pb.TagNumber(4)
+ set streamId($core.String v) {
+ $_setString(3, v);
+ }
+
+ @$pb.TagNumber(4)
+ $core.bool hasStreamId() => $_has(3);
+ @$pb.TagNumber(4)
+ void clearStreamId() => clearField(4);
+
+ @$pb.TagNumber(5)
+ RecognizeStatusType get status => $_getN(4);
+ @$pb.TagNumber(5)
+ set status(RecognizeStatusType v) {
+ setField(5, v);
+ }
+
+ @$pb.TagNumber(5)
+ $core.bool hasStatus() => $_has(4);
+ @$pb.TagNumber(5)
+ void clearStatus() => clearField(5);
+}
+
+class ExecuteInput extends $pb.GeneratedMessage {
+ factory ExecuteInput({
+ $core.String? intent,
+ $core.Iterable<IntentSlot>? intentSlots,
+ }) {
+ final $result = create();
+ if (intent != null) {
+ $result.intent = intent;
+ }
+ if (intentSlots != null) {
+ $result.intentSlots.addAll(intentSlots);
+ }
+ return $result;
+ }
+ ExecuteInput._() : super();
+ factory ExecuteInput.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory ExecuteInput.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'ExecuteInput',
+ createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'intent')
+ ..pc<IntentSlot>(
+ 2, _omitFieldNames ? '' : 'intentSlots', $pb.PbFieldType.PM,
+ subBuilder: IntentSlot.create)
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ ExecuteInput clone() => ExecuteInput()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ ExecuteInput copyWith(void Function(ExecuteInput) updates) =>
+ super.copyWith((message) => updates(message as ExecuteInput))
+ as ExecuteInput;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static ExecuteInput create() => ExecuteInput._();
+ ExecuteInput createEmptyInstance() => create();
+ static $pb.PbList<ExecuteInput> createRepeated() =>
+ $pb.PbList<ExecuteInput>();
+ @$core.pragma('dart2js:noInline')
+ static ExecuteInput getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<ExecuteInput>(create);
+ static ExecuteInput? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get intent => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set intent($core.String v) {
+ $_setString(0, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasIntent() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearIntent() => clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.List<IntentSlot> get intentSlots => $_getList(1);
+}
+
+class ExecuteResult extends $pb.GeneratedMessage {
+ factory ExecuteResult({
+ $core.String? response,
+ ExecuteStatusType? status,
+ }) {
+ final $result = create();
+ if (response != null) {
+ $result.response = response;
+ }
+ if (status != null) {
+ $result.status = status;
+ }
+ return $result;
+ }
+ ExecuteResult._() : super();
+ factory ExecuteResult.fromBuffer($core.List<$core.int> i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(i, r);
+ factory ExecuteResult.fromJson($core.String i,
+ [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(i, r);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'ExecuteResult',
+ createEmptyInstance: create)
+ ..aOS(1, _omitFieldNames ? '' : 'response')
+ ..e<ExecuteStatusType>(
+ 2, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE,
+ defaultOrMaker: ExecuteStatusType.EXEC_ERROR,
+ valueOf: ExecuteStatusType.valueOf,
+ enumValues: ExecuteStatusType.values)
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+ 'Will be removed in next major version')
+ ExecuteResult clone() => ExecuteResult()..mergeFromMessage(this);
+ @$core.Deprecated('Using this can add significant overhead to your binary. '
+ 'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+ 'Will be removed in next major version')
+ ExecuteResult copyWith(void Function(ExecuteResult) updates) =>
+ super.copyWith((message) => updates(message as ExecuteResult))
+ as ExecuteResult;
+
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static ExecuteResult create() => ExecuteResult._();
+ ExecuteResult createEmptyInstance() => create();
+ static $pb.PbList<ExecuteResult> createRepeated() =>
+ $pb.PbList<ExecuteResult>();
+ @$core.pragma('dart2js:noInline')
+ static ExecuteResult getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor<ExecuteResult>(create);
+ static ExecuteResult? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ $core.String get response => $_getSZ(0);
+ @$pb.TagNumber(1)
+ set response($core.String v) {
+ $_setString(0, v);
+ }
+
+ @$pb.TagNumber(1)
+ $core.bool hasResponse() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearResponse() => clearField(1);
+
+ @$pb.TagNumber(2)
+ ExecuteStatusType get status => $_getN(1);
+ @$pb.TagNumber(2)
+ set status(ExecuteStatusType v) {
+ setField(2, v);
+ }
+
+ @$pb.TagNumber(2)
+ $core.bool hasStatus() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearStatus() => clearField(2);
+}
+
+const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
+const _omitMessageNames =
+ $core.bool.fromEnvironment('protobuf.omit_message_names');
diff --git a/lib/grpc/generated/voice_agent.pbenum.dart b/lib/grpc/generated/voice_agent.pbenum.dart
new file mode 100644
index 0000000..a001f03
--- /dev/null
+++ b/lib/grpc/generated/voice_agent.pbenum.dart
@@ -0,0 +1,121 @@
+//
+// Generated code. Do not modify.
+// source: voice_agent.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class RecordAction extends $pb.ProtobufEnum {
+ static const RecordAction START =
+ RecordAction._(0, _omitEnumNames ? '' : 'START');
+ static const RecordAction STOP =
+ RecordAction._(1, _omitEnumNames ? '' : 'STOP');
+
+ static const $core.List<RecordAction> values = <RecordAction>[
+ START,
+ STOP,
+ ];
+
+ static final $core.Map<$core.int, RecordAction> _byValue =
+ $pb.ProtobufEnum.initByValue(values);
+ static RecordAction? valueOf($core.int value) => _byValue[value];
+
+ const RecordAction._($core.int v, $core.String n) : super(v, n);
+}
+
+class NLUModel extends $pb.ProtobufEnum {
+ static const NLUModel SNIPS = NLUModel._(0, _omitEnumNames ? '' : 'SNIPS');
+ static const NLUModel RASA = NLUModel._(1, _omitEnumNames ? '' : 'RASA');
+
+ static const $core.List<NLUModel> values = <NLUModel>[
+ SNIPS,
+ RASA,
+ ];
+
+ static final $core.Map<$core.int, NLUModel> _byValue =
+ $pb.ProtobufEnum.initByValue(values);
+ static NLUModel? valueOf($core.int value) => _byValue[value];
+
+ const NLUModel._($core.int v, $core.String n) : super(v, n);
+}
+
+class RecordMode extends $pb.ProtobufEnum {
+ static const RecordMode MANUAL =
+ RecordMode._(0, _omitEnumNames ? '' : 'MANUAL');
+ static const RecordMode AUTO = RecordMode._(1, _omitEnumNames ? '' : 'AUTO');
+
+ static const $core.List<RecordMode> values = <RecordMode>[
+ MANUAL,
+ AUTO,
+ ];
+
+ static final $core.Map<$core.int, RecordMode> _byValue =
+ $pb.ProtobufEnum.initByValue(values);
+ static RecordMode? valueOf($core.int value) => _byValue[value];
+
+ const RecordMode._($core.int v, $core.String n) : super(v, n);
+}
+
+class RecognizeStatusType extends $pb.ProtobufEnum {
+ static const RecognizeStatusType REC_ERROR =
+ RecognizeStatusType._(0, _omitEnumNames ? '' : 'REC_ERROR');
+ static const RecognizeStatusType REC_SUCCESS =
+ RecognizeStatusType._(1, _omitEnumNames ? '' : 'REC_SUCCESS');
+ static const RecognizeStatusType REC_PROCESSING =
+ RecognizeStatusType._(2, _omitEnumNames ? '' : 'REC_PROCESSING');
+ static const RecognizeStatusType VOICE_NOT_RECOGNIZED =
+ RecognizeStatusType._(3, _omitEnumNames ? '' : 'VOICE_NOT_RECOGNIZED');
+ static const RecognizeStatusType INTENT_NOT_RECOGNIZED =
+ RecognizeStatusType._(4, _omitEnumNames ? '' : 'INTENT_NOT_RECOGNIZED');
+
+ static const $core.List<RecognizeStatusType> values = <RecognizeStatusType>[
+ REC_ERROR,
+ REC_SUCCESS,
+ REC_PROCESSING,
+ VOICE_NOT_RECOGNIZED,
+ INTENT_NOT_RECOGNIZED,
+ ];
+
+ static final $core.Map<$core.int, RecognizeStatusType> _byValue =
+ $pb.ProtobufEnum.initByValue(values);
+ static RecognizeStatusType? valueOf($core.int value) => _byValue[value];
+
+ const RecognizeStatusType._($core.int v, $core.String n) : super(v, n);
+}
+
+class ExecuteStatusType extends $pb.ProtobufEnum {
+ static const ExecuteStatusType EXEC_ERROR =
+ ExecuteStatusType._(0, _omitEnumNames ? '' : 'EXEC_ERROR');
+ static const ExecuteStatusType EXEC_SUCCESS =
+ ExecuteStatusType._(1, _omitEnumNames ? '' : 'EXEC_SUCCESS');
+ static const ExecuteStatusType KUKSA_CONN_ERROR =
+ ExecuteStatusType._(2, _omitEnumNames ? '' : 'KUKSA_CONN_ERROR');
+ static const ExecuteStatusType INTENT_NOT_SUPPORTED =
+ ExecuteStatusType._(3, _omitEnumNames ? '' : 'INTENT_NOT_SUPPORTED');
+ static const ExecuteStatusType INTENT_SLOTS_INCOMPLETE =
+ ExecuteStatusType._(4, _omitEnumNames ? '' : 'INTENT_SLOTS_INCOMPLETE');
+
+ static const $core.List<ExecuteStatusType> values = <ExecuteStatusType>[
+ EXEC_ERROR,
+ EXEC_SUCCESS,
+ KUKSA_CONN_ERROR,
+ INTENT_NOT_SUPPORTED,
+ INTENT_SLOTS_INCOMPLETE,
+ ];
+
+ static final $core.Map<$core.int, ExecuteStatusType> _byValue =
+ $pb.ProtobufEnum.initByValue(values);
+ static ExecuteStatusType? valueOf($core.int value) => _byValue[value];
+
+ const ExecuteStatusType._($core.int v, $core.String n) : super(v, n);
+}
+
+const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
diff --git a/lib/grpc/generated/voice_agent.pbgrpc.dart b/lib/grpc/generated/voice_agent.pbgrpc.dart
new file mode 100644
index 0000000..7984ed3
--- /dev/null
+++ b/lib/grpc/generated/voice_agent.pbgrpc.dart
@@ -0,0 +1,136 @@
+//
+// Generated code. Do not modify.
+// source: voice_agent.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:async' as $async;
+import 'dart:core' as $core;
+
+import 'package:grpc/service_api.dart' as $grpc;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'voice_agent.pb.dart' as $0;
+
+export 'voice_agent.pb.dart';
+
+// @$pb.GrpcServiceName('VoiceAgentService')
+class VoiceAgentServiceClient extends $grpc.Client {
+ static final _$checkServiceStatus =
+ $grpc.ClientMethod<$0.Empty, $0.ServiceStatus>(
+ '/VoiceAgentService/CheckServiceStatus',
+ ($0.Empty value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.ServiceStatus.fromBuffer(value));
+ static final _$detectWakeWord =
+ $grpc.ClientMethod<$0.Empty, $0.WakeWordStatus>(
+ '/VoiceAgentService/DetectWakeWord',
+ ($0.Empty value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.WakeWordStatus.fromBuffer(value));
+ static final _$recognizeVoiceCommand =
+ $grpc.ClientMethod<$0.RecognizeControl, $0.RecognizeResult>(
+ '/VoiceAgentService/RecognizeVoiceCommand',
+ ($0.RecognizeControl value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) =>
+ $0.RecognizeResult.fromBuffer(value));
+ static final _$executeVoiceCommand =
+ $grpc.ClientMethod<$0.ExecuteInput, $0.ExecuteResult>(
+ '/VoiceAgentService/ExecuteVoiceCommand',
+ ($0.ExecuteInput value) => value.writeToBuffer(),
+ ($core.List<$core.int> value) => $0.ExecuteResult.fromBuffer(value));
+
+ VoiceAgentServiceClient($grpc.ClientChannel channel,
+ {$grpc.CallOptions? options,
+ $core.Iterable<$grpc.ClientInterceptor>? interceptors})
+ : super(channel, options: options, interceptors: interceptors);
+
+ $grpc.ResponseFuture<$0.ServiceStatus> checkServiceStatus($0.Empty request,
+ {$grpc.CallOptions? options}) {
+ return $createUnaryCall(_$checkServiceStatus, request, options: options);
+ }
+
+ $grpc.ResponseStream<$0.WakeWordStatus> detectWakeWord($0.Empty request,
+ {$grpc.CallOptions? options}) {
+ return $createStreamingCall(
+ _$detectWakeWord, $async.Stream.fromIterable([request]),
+ options: options);
+ }
+
+ $grpc.ResponseFuture<$0.RecognizeResult> recognizeVoiceCommand(
+ $async.Stream<$0.RecognizeControl> request,
+ {$grpc.CallOptions? options}) {
+ return $createStreamingCall(_$recognizeVoiceCommand, request,
+ options: options)
+ .single;
+ }
+
+ $grpc.ResponseFuture<$0.ExecuteResult> executeVoiceCommand(
+ $0.ExecuteInput request,
+ {$grpc.CallOptions? options}) {
+ return $createUnaryCall(_$executeVoiceCommand, request, options: options);
+ }
+}
+
+// @$pb.GrpcServiceName('VoiceAgentService')
+abstract class VoiceAgentServiceBase extends $grpc.Service {
+ $core.String get $name => 'VoiceAgentService';
+
+ VoiceAgentServiceBase() {
+ $addMethod($grpc.ServiceMethod<$0.Empty, $0.ServiceStatus>(
+ 'CheckServiceStatus',
+ checkServiceStatus_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.Empty.fromBuffer(value),
+ ($0.ServiceStatus value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.Empty, $0.WakeWordStatus>(
+ 'DetectWakeWord',
+ detectWakeWord_Pre,
+ false,
+ true,
+ ($core.List<$core.int> value) => $0.Empty.fromBuffer(value),
+ ($0.WakeWordStatus value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.RecognizeControl, $0.RecognizeResult>(
+ 'RecognizeVoiceCommand',
+ recognizeVoiceCommand,
+ true,
+ false,
+ ($core.List<$core.int> value) => $0.RecognizeControl.fromBuffer(value),
+ ($0.RecognizeResult value) => value.writeToBuffer()));
+ $addMethod($grpc.ServiceMethod<$0.ExecuteInput, $0.ExecuteResult>(
+ 'ExecuteVoiceCommand',
+ executeVoiceCommand_Pre,
+ false,
+ false,
+ ($core.List<$core.int> value) => $0.ExecuteInput.fromBuffer(value),
+ ($0.ExecuteResult value) => value.writeToBuffer()));
+ }
+
+ $async.Future<$0.ServiceStatus> checkServiceStatus_Pre(
+ $grpc.ServiceCall call, $async.Future<$0.Empty> request) async {
+ return checkServiceStatus(call, await request);
+ }
+
+ $async.Stream<$0.WakeWordStatus> detectWakeWord_Pre(
+ $grpc.ServiceCall call, $async.Future<$0.Empty> request) async* {
+ yield* detectWakeWord(call, await request);
+ }
+
+ $async.Future<$0.ExecuteResult> executeVoiceCommand_Pre(
+ $grpc.ServiceCall call, $async.Future<$0.ExecuteInput> request) async {
+ return executeVoiceCommand(call, await request);
+ }
+
+ $async.Future<$0.ServiceStatus> checkServiceStatus(
+ $grpc.ServiceCall call, $0.Empty request);
+ $async.Stream<$0.WakeWordStatus> detectWakeWord(
+ $grpc.ServiceCall call, $0.Empty request);
+ $async.Future<$0.RecognizeResult> recognizeVoiceCommand(
+ $grpc.ServiceCall call, $async.Stream<$0.RecognizeControl> request);
+ $async.Future<$0.ExecuteResult> executeVoiceCommand(
+ $grpc.ServiceCall call, $0.ExecuteInput request);
+}
diff --git a/lib/grpc/generated/voice_agent.pbjson.dart b/lib/grpc/generated/voice_agent.pbjson.dart
new file mode 100644
index 0000000..f4a913c
--- /dev/null
+++ b/lib/grpc/generated/voice_agent.pbjson.dart
@@ -0,0 +1,251 @@
+//
+// Generated code. Do not modify.
+// source: voice_agent.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:convert' as $convert;
+import 'dart:core' as $core;
+import 'dart:typed_data' as $typed_data;
+
+@$core.Deprecated('Use recordActionDescriptor instead')
+const RecordAction$json = {
+ '1': 'RecordAction',
+ '2': [
+ {'1': 'START', '2': 0},
+ {'1': 'STOP', '2': 1},
+ ],
+};
+
+/// Descriptor for `RecordAction`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List recordActionDescriptor =
+ $convert.base64Decode('CgxSZWNvcmRBY3Rpb24SCQoFU1RBUlQQABIICgRTVE9QEAE=');
+
+@$core.Deprecated('Use nLUModelDescriptor instead')
+const NLUModel$json = {
+ '1': 'NLUModel',
+ '2': [
+ {'1': 'SNIPS', '2': 0},
+ {'1': 'RASA', '2': 1},
+ ],
+};
+
+/// Descriptor for `NLUModel`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List nLUModelDescriptor =
+ $convert.base64Decode('CghOTFVNb2RlbBIJCgVTTklQUxAAEggKBFJBU0EQAQ==');
+
+@$core.Deprecated('Use recordModeDescriptor instead')
+const RecordMode$json = {
+ '1': 'RecordMode',
+ '2': [
+ {'1': 'MANUAL', '2': 0},
+ {'1': 'AUTO', '2': 1},
+ ],
+};
+
+/// Descriptor for `RecordMode`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List recordModeDescriptor =
+ $convert.base64Decode('CgpSZWNvcmRNb2RlEgoKBk1BTlVBTBAAEggKBEFVVE8QAQ==');
+
+@$core.Deprecated('Use recognizeStatusTypeDescriptor instead')
+const RecognizeStatusType$json = {
+ '1': 'RecognizeStatusType',
+ '2': [
+ {'1': 'REC_ERROR', '2': 0},
+ {'1': 'REC_SUCCESS', '2': 1},
+ {'1': 'REC_PROCESSING', '2': 2},
+ {'1': 'VOICE_NOT_RECOGNIZED', '2': 3},
+ {'1': 'INTENT_NOT_RECOGNIZED', '2': 4},
+ ],
+};
+
+/// Descriptor for `RecognizeStatusType`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List recognizeStatusTypeDescriptor = $convert.base64Decode(
+ 'ChNSZWNvZ25pemVTdGF0dXNUeXBlEg0KCVJFQ19FUlJPUhAAEg8KC1JFQ19TVUNDRVNTEAESEg'
+ 'oOUkVDX1BST0NFU1NJTkcQAhIYChRWT0lDRV9OT1RfUkVDT0dOSVpFRBADEhkKFUlOVEVOVF9O'
+ 'T1RfUkVDT0dOSVpFRBAE');
+
+@$core.Deprecated('Use executeStatusTypeDescriptor instead')
+const ExecuteStatusType$json = {
+ '1': 'ExecuteStatusType',
+ '2': [
+ {'1': 'EXEC_ERROR', '2': 0},
+ {'1': 'EXEC_SUCCESS', '2': 1},
+ {'1': 'KUKSA_CONN_ERROR', '2': 2},
+ {'1': 'INTENT_NOT_SUPPORTED', '2': 3},
+ {'1': 'INTENT_SLOTS_INCOMPLETE', '2': 4},
+ ],
+};
+
+/// Descriptor for `ExecuteStatusType`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List executeStatusTypeDescriptor = $convert.base64Decode(
+ 'ChFFeGVjdXRlU3RhdHVzVHlwZRIOCgpFWEVDX0VSUk9SEAASEAoMRVhFQ19TVUNDRVNTEAESFA'
+ 'oQS1VLU0FfQ09OTl9FUlJPUhACEhgKFElOVEVOVF9OT1RfU1VQUE9SVEVEEAMSGwoXSU5URU5U'
+ 'X1NMT1RTX0lOQ09NUExFVEUQBA==');
+
+@$core.Deprecated('Use emptyDescriptor instead')
+const Empty$json = {
+ '1': 'Empty',
+};
+
+/// Descriptor for `Empty`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List emptyDescriptor =
+ $convert.base64Decode('CgVFbXB0eQ==');
+
+@$core.Deprecated('Use serviceStatusDescriptor instead')
+const ServiceStatus$json = {
+ '1': 'ServiceStatus',
+ '2': [
+ {'1': 'version', '3': 1, '4': 1, '5': 9, '10': 'version'},
+ {'1': 'status', '3': 2, '4': 1, '5': 8, '10': 'status'},
+ ],
+};
+
+/// Descriptor for `ServiceStatus`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List serviceStatusDescriptor = $convert.base64Decode(
+ 'Cg1TZXJ2aWNlU3RhdHVzEhgKB3ZlcnNpb24YASABKAlSB3ZlcnNpb24SFgoGc3RhdHVzGAIgAS'
+ 'gIUgZzdGF0dXM=');
+
+@$core.Deprecated('Use wakeWordStatusDescriptor instead')
+const WakeWordStatus$json = {
+ '1': 'WakeWordStatus',
+ '2': [
+ {'1': 'status', '3': 1, '4': 1, '5': 8, '10': 'status'},
+ ],
+};
+
+/// Descriptor for `WakeWordStatus`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List wakeWordStatusDescriptor = $convert
+ .base64Decode('Cg5XYWtlV29yZFN0YXR1cxIWCgZzdGF0dXMYASABKAhSBnN0YXR1cw==');
+
+@$core.Deprecated('Use recognizeControlDescriptor instead')
+const RecognizeControl$json = {
+ '1': 'RecognizeControl',
+ '2': [
+ {
+ '1': 'action',
+ '3': 1,
+ '4': 1,
+ '5': 14,
+ '6': '.RecordAction',
+ '10': 'action'
+ },
+ {
+ '1': 'nlu_model',
+ '3': 2,
+ '4': 1,
+ '5': 14,
+ '6': '.NLUModel',
+ '10': 'nluModel'
+ },
+ {
+ '1': 'record_mode',
+ '3': 3,
+ '4': 1,
+ '5': 14,
+ '6': '.RecordMode',
+ '10': 'recordMode'
+ },
+ {'1': 'stream_id', '3': 4, '4': 1, '5': 9, '10': 'streamId'},
+ ],
+};
+
+/// Descriptor for `RecognizeControl`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List recognizeControlDescriptor = $convert.base64Decode(
+ 'ChBSZWNvZ25pemVDb250cm9sEiUKBmFjdGlvbhgBIAEoDjINLlJlY29yZEFjdGlvblIGYWN0aW'
+ '9uEiYKCW5sdV9tb2RlbBgCIAEoDjIJLk5MVU1vZGVsUghubHVNb2RlbBIsCgtyZWNvcmRfbW9k'
+ 'ZRgDIAEoDjILLlJlY29yZE1vZGVSCnJlY29yZE1vZGUSGwoJc3RyZWFtX2lkGAQgASgJUghzdH'
+ 'JlYW1JZA==');
+
+@$core.Deprecated('Use intentSlotDescriptor instead')
+const IntentSlot$json = {
+ '1': 'IntentSlot',
+ '2': [
+ {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
+ {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'},
+ ],
+};
+
+/// Descriptor for `IntentSlot`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List intentSlotDescriptor = $convert.base64Decode(
+ 'CgpJbnRlbnRTbG90EhIKBG5hbWUYASABKAlSBG5hbWUSFAoFdmFsdWUYAiABKAlSBXZhbHVl');
+
+@$core.Deprecated('Use recognizeResultDescriptor instead')
+const RecognizeResult$json = {
+ '1': 'RecognizeResult',
+ '2': [
+ {'1': 'command', '3': 1, '4': 1, '5': 9, '10': 'command'},
+ {'1': 'intent', '3': 2, '4': 1, '5': 9, '10': 'intent'},
+ {
+ '1': 'intent_slots',
+ '3': 3,
+ '4': 3,
+ '5': 11,
+ '6': '.IntentSlot',
+ '10': 'intentSlots'
+ },
+ {'1': 'stream_id', '3': 4, '4': 1, '5': 9, '10': 'streamId'},
+ {
+ '1': 'status',
+ '3': 5,
+ '4': 1,
+ '5': 14,
+ '6': '.RecognizeStatusType',
+ '10': 'status'
+ },
+ ],
+};
+
+/// Descriptor for `RecognizeResult`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List recognizeResultDescriptor = $convert.base64Decode(
+ 'Cg9SZWNvZ25pemVSZXN1bHQSGAoHY29tbWFuZBgBIAEoCVIHY29tbWFuZBIWCgZpbnRlbnQYAi'
+ 'ABKAlSBmludGVudBIuCgxpbnRlbnRfc2xvdHMYAyADKAsyCy5JbnRlbnRTbG90UgtpbnRlbnRT'
+ 'bG90cxIbCglzdHJlYW1faWQYBCABKAlSCHN0cmVhbUlkEiwKBnN0YXR1cxgFIAEoDjIULlJlY2'
+ '9nbml6ZVN0YXR1c1R5cGVSBnN0YXR1cw==');
+
+@$core.Deprecated('Use executeInputDescriptor instead')
+const ExecuteInput$json = {
+ '1': 'ExecuteInput',
+ '2': [
+ {'1': 'intent', '3': 1, '4': 1, '5': 9, '10': 'intent'},
+ {
+ '1': 'intent_slots',
+ '3': 2,
+ '4': 3,
+ '5': 11,
+ '6': '.IntentSlot',
+ '10': 'intentSlots'
+ },
+ ],
+};
+
+/// Descriptor for `ExecuteInput`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List executeInputDescriptor = $convert.base64Decode(
+ 'CgxFeGVjdXRlSW5wdXQSFgoGaW50ZW50GAEgASgJUgZpbnRlbnQSLgoMaW50ZW50X3Nsb3RzGA'
+ 'IgAygLMgsuSW50ZW50U2xvdFILaW50ZW50U2xvdHM=');
+
+@$core.Deprecated('Use executeResultDescriptor instead')
+const ExecuteResult$json = {
+ '1': 'ExecuteResult',
+ '2': [
+ {'1': 'response', '3': 1, '4': 1, '5': 9, '10': 'response'},
+ {
+ '1': 'status',
+ '3': 2,
+ '4': 1,
+ '5': 14,
+ '6': '.ExecuteStatusType',
+ '10': 'status'
+ },
+ ],
+};
+
+/// Descriptor for `ExecuteResult`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List executeResultDescriptor = $convert.base64Decode(
+ 'Cg1FeGVjdXRlUmVzdWx0EhoKCHJlc3BvbnNlGAEgASgJUghyZXNwb25zZRIqCgZzdGF0dXMYAi'
+ 'ABKA4yEi5FeGVjdXRlU3RhdHVzVHlwZVIGc3RhdHVz');
diff --git a/lib/grpc/voice_agent_client.dart b/lib/grpc/voice_agent_client.dart
new file mode 100644
index 0000000..f25f0d2
--- /dev/null
+++ b/lib/grpc/voice_agent_client.dart
@@ -0,0 +1,71 @@
+import 'dart:async';
+import 'package:grpc/grpc.dart';
+import './generated/voice_agent.pbgrpc.dart';
+
+class VoiceAgentClient {
+ late ClientChannel _channel;
+ late VoiceAgentServiceClient _client;
+
+ VoiceAgentClient(String host, int port) {
+ // Initialize the client channel without connecting immediately
+ _channel = ClientChannel(
+ host,
+ port: port,
+ options: ChannelOptions(
+ credentials: ChannelCredentials.insecure(),
+ ),
+ );
+
+ _client = VoiceAgentServiceClient(_channel);
+ }
+
+ Future<ServiceStatus> checkServiceStatus() async {
+ final empty = Empty();
+ try {
+ final response = await _client.checkServiceStatus(empty);
+ return response;
+ } catch (e) {
+ print('Error calling CheckServiceStatus: $e');
+ // Handle the error gracefully, such as returning an error status
+ return ServiceStatus()..status = false;
+ }
+ }
+
+ Stream<WakeWordStatus> detectWakeWord() {
+ final empty = Empty();
+ try {
+ return _client.detectWakeWord(empty);
+ } catch (e) {
+ print('Error calling DetectWakeWord: $e');
+ // Handle the error gracefully, such as returning a default status
+ return Stream.empty(); // An empty stream as a placeholder
+ }
+ }
+
+ Future<RecognizeResult> recognizeVoiceCommand(
+ Stream<RecognizeControl> controlStream) async {
+ try {
+ final response = await _client.recognizeVoiceCommand(controlStream);
+ return response;
+ } catch (e) {
+ print('Error calling RecognizeVoiceCommand: $e');
+ // Handle the error gracefully, such as returning a default RecognizeResult
+ return RecognizeResult()..status = RecognizeStatusType.REC_ERROR;
+ }
+ }
+
+ Future<ExecuteResult> executeVoiceCommand(ExecuteInput input) async {
+ try {
+ final response = await _client.executeVoiceCommand(input);
+ return response;
+ } catch (e) {
+ print('Error calling ExecuteVoiceCommand: $e');
+ // Handle the error gracefully, such as returning an error status
+ return ExecuteResult()..status = ExecuteStatusType.EXEC_ERROR;
+ }
+ }
+
+ Future<void> shutdown() async {
+ await _channel.shutdown();
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
new file mode 100644
index 0000000..bc5b84b
--- /dev/null
+++ b/lib/main.dart
@@ -0,0 +1,77 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'models/app_state.dart';
+import 'screens/home_screen.dart';
+import 'screens/error_screen.dart';
+import 'providers/service_status.dart';
+import 'grpc/generated/voice_agent.pbgrpc.dart';
+import 'grpc/voice_agent_client.dart';
+import 'utils/app_config.dart';
+
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ final serviceStatusProvider = ServiceStatusProvider();
+
+ // get config variables
+ final config = await AppConfig.loadFromAsset();
+
+ // Check the service status initially
+ final initialServiceStatus = await checkServiceStatus(config);
+ serviceStatusProvider.setServiceStatus(initialServiceStatus.status);
+
+ runApp(
+ MultiProvider(
+ providers: [
+ ChangeNotifierProvider(create: (_) => AppState()),
+ ChangeNotifierProvider.value(value: serviceStatusProvider),
+ ],
+ child: App(
+ config: config,
+ onRetry: () async {
+ final serviceStatus = await checkServiceStatus(
+ config); // Retry connecting to the server
+
+ if (serviceStatus.status) {
+ serviceStatusProvider
+ .setServiceStatus(true); // Update the service status
+ }
+ },
+ ),
+ ),
+ );
+}
+
+Future<ServiceStatus> checkServiceStatus(AppConfig config) async {
+ final client = VoiceAgentClient(config.grpcHost, config.grpcPort);
+ final serviceStatus = await client.checkServiceStatus();
+ client.shutdown();
+ return serviceStatus;
+}
+
+class App extends StatelessWidget {
+ final AppConfig config;
+ final VoidCallback onRetry;
+
+ const App({Key? key, required this.config, required this.onRetry});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'AGL Voice Assistant',
+ theme: ThemeData(
+ useMaterial3: true,
+ colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
+ ),
+ home: Consumer<ServiceStatusProvider>(
+ builder: (context, provider, child) {
+ return provider.isServiceOnline
+ ? HomePage(config: config)
+ : ErrorScreen(
+ onRetry: onRetry, // Pass the callback to the ErrorScreen
+ ); // Conditionally render HomePage or ErrorScreen
+ },
+ ),
+ );
+ }
+}
diff --git a/lib/models/app_state.dart b/lib/models/app_state.dart
new file mode 100644
index 0000000..be39bd2
--- /dev/null
+++ b/lib/models/app_state.dart
@@ -0,0 +1,8 @@
+import 'package:flutter/material.dart';
+
+class AppState extends ChangeNotifier {
+ bool isWakeWordDetected = false;
+ bool isWakeWordMode = false;
+ String intentEngine = "snips";
+ String streamId = "";
+}
diff --git a/lib/protos/voice_agent.proto b/lib/protos/voice_agent.proto
new file mode 100644
index 0000000..8c3ab65
--- /dev/null
+++ b/lib/protos/voice_agent.proto
@@ -0,0 +1,83 @@
+syntax = "proto3";
+
+
+service VoiceAgentService {
+ rpc CheckServiceStatus(Empty) returns (ServiceStatus);
+ rpc DetectWakeWord(Empty) returns (stream WakeWordStatus);
+ rpc RecognizeVoiceCommand(stream RecognizeControl) returns (RecognizeResult);
+ rpc ExecuteVoiceCommand(ExecuteInput) returns (ExecuteResult);
+}
+
+
+enum RecordAction {
+ START = 0;
+ STOP = 1;
+}
+
+enum NLUModel {
+ SNIPS = 0;
+ RASA = 1;
+}
+
+enum RecordMode {
+ MANUAL = 0;
+ AUTO = 1;
+}
+
+enum RecognizeStatusType {
+ REC_ERROR = 0;
+ REC_SUCCESS = 1;
+ REC_PROCESSING = 2;
+ VOICE_NOT_RECOGNIZED = 3;
+ INTENT_NOT_RECOGNIZED = 4;
+}
+
+enum ExecuteStatusType {
+ EXEC_ERROR = 0;
+ EXEC_SUCCESS = 1;
+ KUKSA_CONN_ERROR = 2;
+ INTENT_NOT_SUPPORTED = 3;
+ INTENT_SLOTS_INCOMPLETE = 4;
+}
+
+
+message Empty {}
+
+message ServiceStatus {
+ string version = 1;
+ bool status = 2;
+}
+
+message WakeWordStatus {
+ bool status = 1;
+}
+
+message RecognizeControl {
+ RecordAction action = 1;
+ NLUModel nlu_model = 2;
+ RecordMode record_mode = 3;
+ string stream_id = 4;
+}
+
+message IntentSlot {
+ string name = 1;
+ string value = 2;
+}
+
+message RecognizeResult {
+ string command = 1;
+ string intent = 2;
+ repeated IntentSlot intent_slots = 3;
+ string stream_id = 4;
+ RecognizeStatusType status = 5;
+}
+
+message ExecuteInput {
+ string intent = 1;
+ repeated IntentSlot intent_slots = 2;
+}
+
+message ExecuteResult {
+ string response = 1;
+ ExecuteStatusType status = 2;
+}
diff --git a/lib/providers/service_status.dart b/lib/providers/service_status.dart
new file mode 100644
index 0000000..595c92e
--- /dev/null
+++ b/lib/providers/service_status.dart
@@ -0,0 +1,12 @@
+import 'package:flutter/material.dart';
+
+class ServiceStatusProvider extends ChangeNotifier {
+ bool _isServiceOnline = false;
+
+ bool get isServiceOnline => _isServiceOnline;
+
+ void setServiceStatus(bool isOnline) {
+ _isServiceOnline = isOnline;
+ notifyListeners(); // Notify listeners (i.e., widgets that depend on this value) about the change
+ }
+}
diff --git a/lib/screens/error_screen.dart b/lib/screens/error_screen.dart
new file mode 100644
index 0000000..04a5d30
--- /dev/null
+++ b/lib/screens/error_screen.dart
@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+
+class ErrorScreen extends StatelessWidget {
+ final VoidCallback onRetry;
+
+ ErrorScreen({required this.onRetry});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: SizedBox(
+ width: MediaQuery.of(context).size.width * 0.7, // 70% of screen width
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ Icons.error_outline, // Use a Material Icon
+ size: 100,
+ color: Colors.red,
+ ),
+ SizedBox(height: 20), // Add some spacing
+ Text(
+ 'Oops!',
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ color: Colors.red,
+ ),
+ ),
+ SizedBox(height: 10),
+ Text(
+ 'Unable to connect to the voice assistant backend. Make sure the "agl-voiceagent-service" is up and running in server mode with correct config values.',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 18,
+ color: Colors.grey[700],
+ ),
+ ),
+ SizedBox(height: 20),
+ ElevatedButton(
+ onPressed: () {
+ onRetry(); // Call the retry callback
+ },
+ style: ElevatedButton.styleFrom(
+ foregroundColor: Colors.white,
+ backgroundColor: Colors.blue, // Set button color
+ ),
+ child: Text(
+ 'Retry',
+ style: TextStyle(
+ fontSize: 18,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart
new file mode 100644
index 0000000..d19d461
--- /dev/null
+++ b/lib/screens/home_screen.dart
@@ -0,0 +1,398 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'dart:async';
+import '../models/app_state.dart';
+import '../widgets/nlu_engine_choice.dart';
+import '../widgets/assistant_mode_choice.dart';
+import '../widgets/record_command_button.dart';
+import '../widgets/listen_wake_word_section.dart';
+import '../widgets/chat_section.dart';
+import '../grpc/generated/voice_agent.pbgrpc.dart';
+import '../grpc/voice_agent_client.dart';
+import '../utils/app_config.dart';
+
+class HomePage extends StatefulWidget {
+ final AppConfig config;
+
+ HomePage({Key? key, required this.config});
+ @override
+ HomePageState createState() => HomePageState();
+}
+
+class HomePageState extends State<HomePage> {
+ late AppConfig _config; // Store the config as an instance variable
+ final ScrollController _scrollController = ScrollController();
+ List<ChatMessage> chatMessages = [];
+ StreamSubscription<WakeWordStatus>? _wakeWordStatusSubscription;
+ late VoiceAgentClient voiceAgentClient;
+
+ @override
+ void initState() {
+ super.initState();
+ _config = widget.config; // Initialize _config in the initState
+ addChatMessage(
+ "Assistant in Manual mode. You can send commands directly by pressing the record button.");
+ }
+
+ void changeAssistantMode(BuildContext context, AssistantMode newMode) {
+ final appState = context.read<AppState>();
+ clearChatMessages();
+ appState.streamId = "";
+ appState.isWakeWordDetected = false;
+
+ if (newMode == AssistantMode.wakeWord) {
+ appState.isWakeWordMode = true;
+ addChatMessage(
+ 'Switched to Wake Word mode. I\'ll listen for the wake word before responding.');
+ toggleWakeWordDetection(context, true);
+ } else if (newMode == AssistantMode.manual) {
+ appState.isWakeWordMode = false;
+ addChatMessage(
+ 'Switched to Manual mode. You can send commands directly by pressing record button.');
+ toggleWakeWordDetection(context, false);
+ }
+ print(appState.isWakeWordMode);
+ setState(() {}); // Trigger a rebuild
+ }
+
+ void changeIntentEngine(BuildContext context, NLUEngine newEngine) {
+ final appState = context.read<AppState>();
+
+ if (newEngine == NLUEngine.snips) {
+ appState.intentEngine = "snips";
+ addChatMessage(
+ 'Switched to 🚀 Snips engine. Lets be precise and accurate.');
+ } else if (newEngine == NLUEngine.rasa) {
+ appState.intentEngine = "rasa";
+ addChatMessage(
+ 'Switched to 🤖 RASA engine. Conversations just got smarter!');
+ }
+ print(appState.intentEngine);
+ setState(() {}); // Trigger a rebuild
+ }
+
+ void addChatMessage(String text, {bool isUserMessage = false}) {
+ final newMessage = ChatMessage(text: text, isUserMessage: isUserMessage);
+ setState(() {
+ chatMessages.add(newMessage);
+ });
+ // Use a post-frame callback to scroll after the frame has been rendered
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _scrollController.animateTo(
+ _scrollController.position.maxScrollExtent,
+ duration: Duration(milliseconds: 300),
+ curve: Curves.easeInOut,
+ );
+ });
+ }
+
+ // Function to clear all chat messages
+ void clearChatMessages() {
+ setState(() {
+ chatMessages.clear();
+ });
+ }
+
+ void changeCommandRecordingState(
+ BuildContext context, bool isRecording) async {
+ final appState = context.read<AppState>();
+ if (isRecording) {
+ appState.streamId = await startRecording();
+ } else {
+ final response =
+ await stopRecording(appState.streamId, appState.intentEngine);
+ // Process and store the result
+ if (response.status == RecognizeStatusType.REC_SUCCESS) {
+ await executeVoiceCommand(
+ response); // Call executeVoiceCommand with the response
+ }
+ }
+ }
+
+ // gRPC related methods are as follows
+ // Function to start and stop the wake word detection loop
+ void toggleWakeWordDetection(BuildContext context, bool startDetection) {
+ final appState = context.read<AppState>();
+ if (startDetection) {
+ appState.isWakeWordDetected = false;
+ _startWakeWordDetection(context);
+ } else {
+ _stopWakeWordDetection();
+ }
+ }
+
+ // Function to start listening for wake word status responses
+ void _startWakeWordDetection(BuildContext context) {
+ final appState = context.read<AppState>();
+ voiceAgentClient = VoiceAgentClient(_config.grpcHost, _config.grpcPort);
+ _wakeWordStatusSubscription = voiceAgentClient.detectWakeWord().listen(
+ (response) {
+ if (response.status) {
+ // Wake word detected, you can handle this case here
+ // Set _isDetectingWakeWord to false to stop the loop
+ _stopWakeWordDetection();
+ appState.isWakeWordDetected = true;
+ addChatMessage(
+ 'Wake word detected! Now you can send your command by pressing the record button.');
+ setState(() {}); // Trigger a rebuild
+ }
+ },
+ onError: (error) {
+ // Handle any errors that occur during wake word detection
+ print('Error during wake word detection: $error');
+ // Set _isDetectingWakeWord to false to stop the loop
+ _stopWakeWordDetection();
+ },
+ cancelOnError: true,
+ );
+ }
+
+ // Function to stop listening for wake word status responses
+ void _stopWakeWordDetection() {
+ _wakeWordStatusSubscription?.cancel();
+ voiceAgentClient.shutdown();
+ }
+
+ Future<String> startRecording() async {
+ String streamId = "";
+ voiceAgentClient = VoiceAgentClient(_config.grpcHost, _config.grpcPort);
+ try {
+ // Create a RecognizeControl message to start recording
+ final controlMessage = RecognizeControl()
+ ..action = RecordAction.START
+ ..recordMode = RecordMode
+ .MANUAL; // You can change this to your desired record mode
+
+ // Create a Stream with the control message
+ final controlStream = Stream.fromIterable([controlMessage]);
+
+ // Call the gRPC method to start recording
+ final response =
+ await voiceAgentClient.recognizeVoiceCommand(controlStream);
+
+ streamId = response.streamId;
+ } catch (e) {
+ print('Error starting recording: $e');
+ addChatMessage('Recording failed. Please try again.');
+ }
+ return streamId;
+ }
+
+ Future<RecognizeResult> stopRecording(
+ String streamId, String nluModel) async {
+ try {
+ NLUModel model = NLUModel.RASA;
+ if (nluModel == "snips") {
+ model = NLUModel.SNIPS;
+ }
+ // Create a RecognizeControl message to stop recording
+ final controlMessage = RecognizeControl()
+ ..action = RecordAction.STOP
+ ..nluModel = model
+ ..streamId =
+ streamId // Use the same stream ID as when starting recording
+ ..recordMode = RecordMode.MANUAL;
+
+ // Create a Stream with the control message
+ final controlStream = Stream.fromIterable([controlMessage]);
+
+ // Call the gRPC method to stop recording
+ final response =
+ await voiceAgentClient.recognizeVoiceCommand(controlStream);
+
+ // Process and store the result
+ if (response.status == RecognizeStatusType.REC_SUCCESS) {
+ final command = response.command;
+ // final intent = response.intent;
+ // final intentSlots = response.intentSlots;
+ addChatMessage(command, isUserMessage: true);
+ } else if (response.status == RecognizeStatusType.INTENT_NOT_RECOGNIZED) {
+ final command = response.command;
+ addChatMessage(command, isUserMessage: true);
+ addChatMessage(
+ "Unable to undertsand the intent behind your request. Please try again.");
+ } else {
+ addChatMessage(
+ 'Failed to process your voice command. Please try again.');
+ }
+ await voiceAgentClient.shutdown();
+ return response;
+ } catch (e) {
+ print('Error stopping recording: $e');
+ addChatMessage('Failed to process your voice command. Please try again.');
+ await voiceAgentClient.shutdown();
+ return RecognizeResult()..status = RecognizeStatusType.REC_ERROR;
+ }
+ // await voiceAgentClient.shutdown();
+ }
+
+ Future<void> executeVoiceCommand(RecognizeResult response) async {
+ voiceAgentClient = VoiceAgentClient(_config.grpcHost, _config.grpcPort);
+ try {
+ // Create an ExecuteInput message using the response from stopRecording
+ final executeInput = ExecuteInput()
+ ..intent = response.intent
+ ..intentSlots.addAll(response.intentSlots);
+
+ // Call the gRPC method to execute the voice command
+ final execResponse =
+ await voiceAgentClient.executeVoiceCommand(executeInput);
+
+ // Handle the response as needed
+ if (execResponse.status == ExecuteStatusType.EXEC_SUCCESS) {
+ final commandResponse = execResponse.response;
+ addChatMessage(commandResponse);
+ } else if (execResponse.status == ExecuteStatusType.KUKSA_CONN_ERROR) {
+ final commandResponse = execResponse.response;
+ addChatMessage(commandResponse);
+ } else {
+ // Handle the case when execution fails
+ addChatMessage(
+ 'Failed to execute your voice command. Please try again.');
+ }
+ } catch (e) {
+ print('Error executing voice command: $e');
+ // Handle any errors that occur during the gRPC call
+ addChatMessage('Failed to execute your voice command. Please try again.');
+ }
+ await voiceAgentClient.shutdown();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final appState = context.watch<AppState>();
+
+ return Scaffold(
+ body: SingleChildScrollView(
+ child: Center(
+ child: SizedBox(
+ width:
+ MediaQuery.of(context).size.width * 0.85, // 85% of screen width
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Image.asset(
+ 'assets/agl_logo.png', // Replace with your logo image path
+ width: 120, // Adjust the width as needed
+ height: 120, // Adjust the height as needed
+ ),
+ Text(
+ "AGL Voice Assistant",
+ style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
+ ),
+ SizedBox(height: 30),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Flexible(
+ flex: 1,
+ child: Card(
+ elevation: 4, // Add elevation for shadow
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Assistant Mode',
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(height: 16), // Add spacing if needed
+ Center(
+ child: Consumer<AppState>(
+ builder: (context, appState, _) {
+ return AssistantModeChoice(
+ onModeChanged: (newMode) {
+ changeAssistantMode(context, newMode);
+ print(newMode);
+ },
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+
+ SizedBox(width: 20), // Add spacing between buttons
+
+ Flexible(
+ flex: 1,
+ child: Card(
+ elevation: 4, // Add elevation for shadow
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: Padding(
+ padding: EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Intent Engine',
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(height: 16), // Add spacing if needed
+ Center(
+ child: Consumer<AppState>(
+ builder: (context, appState, _) {
+ return NLUEngineChoice(
+ onEngineChanged: (newEngine) {
+ changeIntentEngine(context, newEngine);
+ print(newEngine);
+ },
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 30),
+ ChatSection(
+ scrollController: _scrollController,
+ chatMessages: chatMessages,
+ addChatMessage: addChatMessage,
+ ),
+ SizedBox(height: 30),
+ if (!appState.isWakeWordMode || appState.isWakeWordDetected)
+ Center(
+ child: Consumer<AppState>(builder: (context, appState, _) {
+ return RecordCommandButton(
+ onRecordingStateChanged: (isRecording) {
+ changeCommandRecordingState(context, isRecording);
+ },
+ );
+ }),
+ )
+ else
+ Center(
+ child: Consumer<AppState>(
+ builder: (context, appState, _) {
+ return ListeningForWakeWordSection();
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/utils/app_config.dart b/lib/utils/app_config.dart
new file mode 100644
index 0000000..8f5c566
--- /dev/null
+++ b/lib/utils/app_config.dart
@@ -0,0 +1,26 @@
+import 'dart:convert';
+import 'dart:async';
+import 'package:flutter/services.dart';
+
+class AppConfig {
+ late String grpcHost;
+ late int grpcPort;
+
+ AppConfig({required this.grpcHost, required this.grpcPort});
+
+ factory AppConfig.fromAsset() {
+ return AppConfig._();
+ }
+
+ AppConfig._();
+
+ static Future<AppConfig> loadFromAsset() async {
+ final configString = await rootBundle.loadString('assets/config.json');
+ final jsonMap = json.decode(configString);
+
+ return AppConfig(
+ grpcHost: jsonMap['grpc_host'],
+ grpcPort: jsonMap['grpc_port'],
+ );
+ }
+}
diff --git a/lib/widgets/assistant_mode_choice.dart b/lib/widgets/assistant_mode_choice.dart
new file mode 100644
index 0000000..ec17534
--- /dev/null
+++ b/lib/widgets/assistant_mode_choice.dart
@@ -0,0 +1,202 @@
+// import 'package:flutter/material.dart';
+// import 'package:provider/provider.dart';
+// import '../models/app_state.dart';
+
+// enum AssistantMode { wakeWord, manual }
+
+// class AssistantModeChoice extends StatefulWidget {
+// final Function(AssistantMode) onModeChanged;
+
+// const AssistantModeChoice({Key? key, required this.onModeChanged})
+// : super(key: key);
+
+// @override
+// AssistantModeChoiceState createState() => AssistantModeChoiceState();
+// }
+
+// class AssistantModeChoiceState extends State<AssistantModeChoice> {
+// @override
+// Widget build(BuildContext context) {
+// final appState = context.watch<AppState>(); // Watch the app state
+
+// return SegmentedButton<AssistantMode>(
+// segments: const <ButtonSegment<AssistantMode>>[
+// ButtonSegment<AssistantMode>(
+// value: AssistantMode.wakeWord,
+// label: Text('Wake Word'),
+// icon: Icon(Icons.graphic_eq)),
+// ButtonSegment<AssistantMode>(
+// value: AssistantMode.manual,
+// label: Text('Manual'),
+// icon: Icon(Icons.graphic_eq)),
+// ],
+// selected: <AssistantMode>{
+// appState.isWakeWordMode ? AssistantMode.wakeWord : AssistantMode.manual
+// }, // Use app state
+// onSelectionChanged: (Set<AssistantMode> newSelection) {
+// final newMode = newSelection.first;
+// setState(() {
+// // Update the app state when the mode changes
+// appState.isWakeWordMode = newMode == AssistantMode.wakeWord;
+// });
+// // Call the callback function to notify the mode change
+// widget.onModeChanged(newMode);
+// },
+// style: ButtonStyle(
+// side: MaterialStateProperty.all<BorderSide>(
+// BorderSide(
+// width: 0, // Remove border width
+// color: Colors.transparent, // Make border transparent
+// ),
+// ),
+// backgroundColor: MaterialStateProperty.resolveWith<Color>(
+// (states) {
+// if (states.contains(MaterialState.selected)) {
+// return Colors.green; // Color when pressed
+// }
+// // Add more conditions for other states as needed
+// return Colors.white; // Default color
+// },
+// ),
+// foregroundColor: MaterialStateProperty.resolveWith<Color>((states) {
+// if (states.contains(MaterialState.selected)) {
+// return Colors.white; // Color when pressed
+// }
+// // Add more conditions for other states as needed
+// return Colors.green;
+// })),
+// );
+// }
+// }
+
+import 'package:flutter/material.dart';
+
+enum AssistantMode { wakeWord, manual }
+
+class AssistantModeChoice extends StatefulWidget {
+ final Function(AssistantMode) onModeChanged;
+
+ const AssistantModeChoice({Key? key, required this.onModeChanged})
+ : super(key: key);
+
+ @override
+ AssistantModeChoiceState createState() => AssistantModeChoiceState();
+}
+
+class AssistantModeChoiceState extends State<AssistantModeChoice> {
+ late AssistantMode _selectedMode;
+
+ @override
+ void initState() {
+ super.initState();
+ _selectedMode = AssistantMode.manual; // Initialize the selection
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ InkWell(
+ onTap: () => _onModeChanged(AssistantMode.wakeWord),
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(20.0),
+ bottomLeft: Radius.circular(20.0),
+ ),
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 17.5, vertical: 5.0),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(20.0),
+ bottomLeft: Radius.circular(20.0),
+ ),
+ color: _selectedMode == AssistantMode.wakeWord
+ ? Colors.green
+ : Colors.white,
+ border: Border.all(
+ color: Colors.transparent,
+ ),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ _selectedMode == AssistantMode.wakeWord
+ ? Icons.check
+ : Icons.graphic_eq,
+ color: _selectedMode == AssistantMode.wakeWord
+ ? Colors.white
+ : Colors.green,
+ ),
+ SizedBox(width: 8),
+ Text(
+ 'Wake Word',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 18,
+ color: _selectedMode == AssistantMode.wakeWord
+ ? Colors.white
+ : Colors.green,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ InkWell(
+ onTap: () => _onModeChanged(AssistantMode.manual),
+ borderRadius: BorderRadius.only(
+ topRight: Radius.circular(20.0),
+ bottomRight: Radius.circular(20.0),
+ ),
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 17.5, vertical: 5.0),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.only(
+ topRight: Radius.circular(20.0),
+ bottomRight: Radius.circular(20.0),
+ ),
+ color: _selectedMode == AssistantMode.manual
+ ? Colors.green
+ : Colors.white,
+ border: Border.all(
+ color: Colors.transparent,
+ ),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ _selectedMode == AssistantMode.manual
+ ? Icons.check
+ : Icons.graphic_eq,
+ color: _selectedMode == AssistantMode.manual
+ ? Colors.white
+ : Colors.green,
+ ),
+ SizedBox(width: 8),
+ Text(
+ 'Manual',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 18,
+ color: _selectedMode == AssistantMode.manual
+ ? Colors.white
+ : Colors.green,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+
+ void _onModeChanged(AssistantMode newMode) {
+ setState(() {
+ _selectedMode = newMode;
+ });
+
+ // Call the callback function to notify the mode change
+ widget.onModeChanged(newMode);
+ }
+}
diff --git a/lib/widgets/chat_section.dart b/lib/widgets/chat_section.dart
new file mode 100644
index 0000000..596b9f3
--- /dev/null
+++ b/lib/widgets/chat_section.dart
@@ -0,0 +1,128 @@
+import 'package:flutter/material.dart';
+
+class ChatSection extends StatelessWidget {
+ final ScrollController scrollController;
+ final List<ChatMessage> chatMessages;
+ final Function(String text, {bool isUserMessage}) addChatMessage;
+
+ ChatSection({
+ required this.scrollController,
+ required this.chatMessages,
+ required this.addChatMessage,
+ });
+
+ @override
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ elevation: 4, // Add a subtle shadow
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: Column(
+ children: [
+ // Chat heading
+ Container(
+ padding: EdgeInsets.all(6),
+ alignment: Alignment.center,
+ child: Text(
+ 'Command Logs',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 20,
+ ),
+ ),
+ ),
+ // Chat messages with fixed height
+ Container(
+ padding: EdgeInsets.all(12),
+ height: 180, // Adjust the height as needed
+ child: ListView.builder(
+ controller: scrollController,
+ itemCount: chatMessages.length,
+ itemBuilder: (context, index) {
+ final message = chatMessages[index];
+ return ChatMessageTile(message: message);
+ },
+ ),
+ ),
+ // User input field (if needed)
+ // ...
+ ],
+ ),
+ );
+ }
+}
+
+class ChatMessage {
+ final String text;
+ final bool isUserMessage;
+
+ ChatMessage({required this.text, this.isUserMessage = false});
+}
+
+class ChatMessageTile extends StatelessWidget {
+ final ChatMessage message;
+
+ ChatMessageTile({required this.message});
+
+ @override
+ Widget build(BuildContext context) {
+ return ListTile(
+ contentPadding: EdgeInsets.all(0),
+ title: Container(
+ alignment:
+ message.isUserMessage ? Alignment.topRight : Alignment.topLeft,
+ child: Row(
+ mainAxisAlignment: message.isUserMessage
+ ? MainAxisAlignment.end
+ : MainAxisAlignment.start,
+ children: [
+ if (!message.isUserMessage)
+ CircleAvatar(
+ backgroundColor: Colors.green[400],
+ child: Icon(
+ Icons.smart_toy_outlined,
+ color: Colors.white,
+ ),
+ ),
+ SizedBox(width: 8),
+ Flexible(
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(16),
+ topRight: Radius.circular(16),
+ bottomLeft: message.isUserMessage
+ ? Radius.circular(16)
+ : Radius.circular(0),
+ bottomRight: message.isUserMessage
+ ? Radius.circular(0)
+ : Radius.circular(16),
+ ),
+ color:
+ message.isUserMessage ? Colors.blue : Colors.green[400],
+ ),
+ child: Text(
+ message.text,
+ style: TextStyle(color: Colors.white, fontSize: 18),
+ maxLines: null,
+ ),
+ ),
+ ),
+ SizedBox(width: 8),
+ if (message.isUserMessage)
+ CircleAvatar(
+ backgroundColor: Colors.blue,
+ child: Icon(
+ Icons.person,
+ color: Colors.white,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/listen_wake_word_section.dart b/lib/widgets/listen_wake_word_section.dart
new file mode 100644
index 0000000..61abcd0
--- /dev/null
+++ b/lib/widgets/listen_wake_word_section.dart
@@ -0,0 +1,66 @@
+import 'package:flutter/material.dart';
+
+class ListeningForWakeWordSection extends StatefulWidget {
+ @override
+ ListeningForWakeWordSectionState createState() =>
+ ListeningForWakeWordSectionState();
+}
+
+class ListeningForWakeWordSectionState
+ extends State<ListeningForWakeWordSection>
+ with SingleTickerProviderStateMixin {
+ late AnimationController _controller;
+ late Animation<Color?> _colorAnimation;
+
+ @override
+ void initState() {
+ super.initState();
+
+ // Create an animation controller
+ _controller = AnimationController(
+ vsync: this,
+ duration: Duration(milliseconds: 1500), // Adjust the duration as needed
+ );
+
+ // Create a color change animation
+ _colorAnimation = ColorTween(
+ begin: Colors.orangeAccent, // Use your desired initial color
+ end: Colors.redAccent, // Use your desired final color
+ ).animate(_controller);
+
+ // Start both animations
+ _controller.repeat(reverse: true);
+ }
+
+ @override
+ void dispose() {
+ _controller.dispose(); // Dispose of the animation controller
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ AnimatedBuilder(
+ animation: _controller,
+ builder: (context, child) {
+ return Icon(
+ Icons.album, // Replace with your listening icon
+ size: 60,
+ color: _colorAnimation.value,
+ );
+ },
+ ),
+ SizedBox(height: 8),
+ Text(
+ 'Listening for wake word...',
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/widgets/nlu_engine_choice.dart b/lib/widgets/nlu_engine_choice.dart
new file mode 100644
index 0000000..1e8ca52
--- /dev/null
+++ b/lib/widgets/nlu_engine_choice.dart
@@ -0,0 +1,200 @@
+// import 'package:flutter/material.dart';
+
+// enum NLUEngine { snips, rasa }
+
+// class NLUEngineChoice extends StatefulWidget {
+// final Function(NLUEngine) onEngineChanged;
+
+// const NLUEngineChoice({Key? key, required this.onEngineChanged})
+// : super(key: key);
+
+// @override
+// State<NLUEngineChoice> createState() => _NLUEngineChoiceState();
+// }
+
+// class _NLUEngineChoiceState extends State<NLUEngineChoice> {
+// NLUEngine nluView = NLUEngine.snips;
+
+// @override
+// Widget build(BuildContext context) {
+// return SegmentedButton<NLUEngine>(
+// segments: const <ButtonSegment<NLUEngine>>[
+// ButtonSegment<NLUEngine>(
+// value: NLUEngine.snips,
+// label: Text('Snips'),
+// icon: Icon(Icons.settings_suggest)),
+// ButtonSegment<NLUEngine>(
+// value: NLUEngine.rasa,
+// label: Text('RASA'),
+// icon: Icon(Icons.settings_suggest)),
+// ],
+// selected: <NLUEngine>{nluView},
+// onSelectionChanged: (Set<NLUEngine> newSelection) {
+// final newEngine = newSelection.first;
+// setState(() {
+// // By default there is only a single segment that can be
+// // selected at one time, so its value is always the first
+// // item in the selected set.
+// nluView = newEngine;
+// });
+// // Call the callback function to notify the mode change
+// widget.onEngineChanged(newEngine);
+// },
+// style: ButtonStyle(
+// side: MaterialStateProperty.all<BorderSide>(
+// BorderSide(
+// width: 0, // Remove border width
+// color: Colors.transparent, // Make border transparent
+// ),
+// ),
+// backgroundColor: MaterialStateProperty.resolveWith<Color>(
+// (states) {
+// if (states.contains(MaterialState.selected)) {
+// return Colors.green; // Color when pressed
+// }
+// // Add more conditions for other states as needed
+// return Colors.white; // Default color
+// },
+// ),
+// foregroundColor: MaterialStateProperty.resolveWith<Color>((states) {
+// if (states.contains(MaterialState.selected)) {
+// return Colors.white; // Color when pressed
+// }
+// // Add more conditions for other states as needed
+// return Colors.green;
+// })),
+// );
+// }
+// }
+
+import 'package:flutter/material.dart';
+
+enum NLUEngine { snips, rasa }
+
+class NLUEngineChoice extends StatefulWidget {
+ final Function(NLUEngine) onEngineChanged;
+
+ const NLUEngineChoice({Key? key, required this.onEngineChanged})
+ : super(key: key);
+
+ @override
+ State<NLUEngineChoice> createState() => _NLUEngineChoiceState();
+}
+
+class _NLUEngineChoiceState extends State<NLUEngineChoice> {
+ late NLUEngine _selectedEngine;
+
+ @override
+ void initState() {
+ super.initState();
+ _selectedEngine = NLUEngine.snips; // Initialize the selection
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ InkWell(
+ onTap: () => _onEngineChanged(NLUEngine.snips),
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(20.0),
+ bottomLeft: Radius.circular(20.0),
+ ),
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 17.5, vertical: 5.0),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(20.0),
+ bottomLeft: Radius.circular(20.0),
+ ),
+ color: _selectedEngine == NLUEngine.snips
+ ? Colors.green
+ : Colors.white,
+ border: Border.all(
+ color: Colors.transparent,
+ ),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ _selectedEngine == NLUEngine.snips
+ ? Icons.check
+ : Icons.settings_suggest,
+ color: _selectedEngine == NLUEngine.snips
+ ? Colors.white
+ : Colors.green,
+ ),
+ SizedBox(width: 8),
+ Text(
+ 'Snips',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 18,
+ color: _selectedEngine == NLUEngine.snips
+ ? Colors.white
+ : Colors.green,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ InkWell(
+ onTap: () => _onEngineChanged(NLUEngine.rasa),
+ borderRadius: BorderRadius.only(
+ topRight: Radius.circular(20.0),
+ bottomRight: Radius.circular(20.0),
+ ),
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 17.5, vertical: 5.0),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.only(
+ topRight: Radius.circular(20.0),
+ bottomRight: Radius.circular(20.0),
+ ),
+ color: _selectedEngine == NLUEngine.rasa
+ ? Colors.green
+ : Colors.white,
+ border: Border.all(
+ color: Colors.transparent,
+ ),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ _selectedEngine == NLUEngine.rasa
+ ? Icons.check
+ : Icons.settings_suggest,
+ color: _selectedEngine == NLUEngine.rasa
+ ? Colors.white
+ : Colors.green,
+ ),
+ SizedBox(width: 8),
+ Text(
+ 'RASA',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 18,
+ color: _selectedEngine == NLUEngine.rasa
+ ? Colors.white
+ : Colors.green,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+
+ void _onEngineChanged(NLUEngine newEngine) {
+ setState(() {
+ _selectedEngine = newEngine;
+ });
+
+ // Call the callback function to notify the engine change
+ widget.onEngineChanged(newEngine);
+ }
+}
diff --git a/lib/widgets/record_command_button.dart b/lib/widgets/record_command_button.dart
new file mode 100644
index 0000000..fdff772
--- /dev/null
+++ b/lib/widgets/record_command_button.dart
@@ -0,0 +1,66 @@
+import 'package:flutter/material.dart';
+
+class RecordCommandButton extends StatefulWidget {
+ final ValueChanged<bool> onRecordingStateChanged;
+
+ RecordCommandButton({required this.onRecordingStateChanged});
+
+ @override
+ RecordCommandButtonState createState() => RecordCommandButtonState();
+}
+
+class RecordCommandButtonState extends State<RecordCommandButton> {
+ bool isRecording = false;
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ AnimatedContainer(
+ duration: Duration(seconds: 1),
+ curve: Curves.easeInOut,
+ width: 60, // Adjust the button size as needed
+ height: 60, // Adjust the button size as needed
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: isRecording
+ ? Colors.red
+ : Colors.green, // Green when recording, red when not recording
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.5),
+ blurRadius: 5,
+ spreadRadius: 1,
+ ),
+ ],
+ ),
+ child: InkWell(
+ onTap: () {
+ // Toggle recording state
+ setState(() {
+ isRecording = !isRecording;
+ });
+ // Call the callback function with the recording state
+ widget.onRecordingStateChanged(isRecording);
+ },
+ child: Center(
+ child: Icon(
+ Icons.mic, // Microphone icon
+ size: 36, // Icon size
+ color: Colors.white, // Icon color
+ ),
+ ),
+ ),
+ ),
+ SizedBox(height: 8), // Add spacing between the button and text
+ Text(
+ isRecording ? 'Recording...' : 'Record Command',
+ style: TextStyle(
+ fontSize: 18, // Text size
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/linux/.gitignore b/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt
new file mode 100644
index 0000000..9ebec8b
--- /dev/null
+++ b/linux/CMakeLists.txt
@@ -0,0 +1,139 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "flutter_voiceassistant")
+# The unique GTK application identifier for this application. See:
+# https://wiki.gnome.org/HowDoI/ChooseApplicationID
+set(APPLICATION_ID "com.example.flutter_voiceassistant")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Load bundled libraries from the lib/ directory relative to the binary.
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+ set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+ set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Define build configuration options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE
+ STRING "Flutter build mode" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+ target_compile_features(${TARGET} PUBLIC cxx_std_14)
+ target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+ target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+ target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Define the application target. To change its name, change BINARY_NAME above,
+# not the value here, or `flutter run` will no longer work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME}
+ "main.cc"
+ "my_application.cc"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add dependency libraries. Add any application-specific dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
+
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+ PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+ file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+ " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
+ install(FILES "${bundled_library}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endforeach(bundled_library)
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+ file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+ " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+ DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+ install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..d5bd016
--- /dev/null
+++ b/linux/flutter/CMakeLists.txt
@@ -0,0 +1,88 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+ set(NEW_LIST "")
+ foreach(element ${${LIST_NAME}})
+ list(APPEND NEW_LIST "${PREFIX}${element}")
+ endforeach(element)
+ set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+ "fl_basic_message_channel.h"
+ "fl_binary_codec.h"
+ "fl_binary_messenger.h"
+ "fl_dart_project.h"
+ "fl_engine.h"
+ "fl_json_message_codec.h"
+ "fl_json_method_codec.h"
+ "fl_message_codec.h"
+ "fl_method_call.h"
+ "fl_method_channel.h"
+ "fl_method_codec.h"
+ "fl_method_response.h"
+ "fl_plugin_registrar.h"
+ "fl_plugin_registry.h"
+ "fl_standard_message_codec.h"
+ "fl_standard_method_codec.h"
+ "fl_string_codec.h"
+ "fl_value.h"
+ "fl_view.h"
+ "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+ "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+ PkgConfig::GTK
+ PkgConfig::GLIB
+ PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+ OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+ ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+ COMMAND ${CMAKE_COMMAND} -E env
+ ${FLUTTER_TOOL_ENVIRONMENT}
+ "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+ ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+ VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+ "${FLUTTER_LIBRARY}"
+ ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..e71a16d
--- /dev/null
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,11 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+}
diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..e0f0a47
--- /dev/null
+++ b/linux/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter_linux/flutter_linux.h>
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif // GENERATED_PLUGIN_REGISTRANT_
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..2e1de87
--- /dev/null
+++ b/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,23 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+ target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/linux/main.cc b/linux/main.cc
new file mode 100644
index 0000000..e7c5c54
--- /dev/null
+++ b/linux/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+ g_autoptr(MyApplication) app = my_application_new();
+ return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/linux/my_application.cc b/linux/my_application.cc
new file mode 100644
index 0000000..abbc952
--- /dev/null
+++ b/linux/my_application.cc
@@ -0,0 +1,104 @@
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+ GtkApplication parent_instance;
+ char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+ MyApplication* self = MY_APPLICATION(application);
+ GtkWindow* window =
+ GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+ // Use a header bar when running in GNOME as this is the common style used
+ // by applications and is the setup most users will be using (e.g. Ubuntu
+ // desktop).
+ // If running on X and not using GNOME then just use a traditional title bar
+ // in case the window manager does more exotic layout, e.g. tiling.
+ // If running on Wayland assume the header bar will work (may need changing
+ // if future cases occur).
+ gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+ GdkScreen* screen = gtk_window_get_screen(window);
+ if (GDK_IS_X11_SCREEN(screen)) {
+ const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+ if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+ use_header_bar = FALSE;
+ }
+ }
+#endif
+ if (use_header_bar) {
+ GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_show(GTK_WIDGET(header_bar));
+ gtk_header_bar_set_title(header_bar, "flutter_voiceassistant");
+ gtk_header_bar_set_show_close_button(header_bar, TRUE);
+ gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+ } else {
+ gtk_window_set_title(window, "flutter_voiceassistant");
+ }
+
+ gtk_window_set_default_size(window, 1280, 720);
+ gtk_widget_show(GTK_WIDGET(window));
+
+ g_autoptr(FlDartProject) project = fl_dart_project_new();
+ fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
+
+ FlView* view = fl_view_new(project);
+ gtk_widget_show(GTK_WIDGET(view));
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+ fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+ gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
+ MyApplication* self = MY_APPLICATION(application);
+ // Strip out the first argument as it is the binary name.
+ self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+ g_autoptr(GError) error = nullptr;
+ if (!g_application_register(application, nullptr, &error)) {
+ g_warning("Failed to register: %s", error->message);
+ *exit_status = 1;
+ return TRUE;
+ }
+
+ g_application_activate(application);
+ *exit_status = 0;
+
+ return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+ MyApplication* self = MY_APPLICATION(object);
+ g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+ G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+ G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+ G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
+ G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+ return MY_APPLICATION(g_object_new(my_application_get_type(),
+ "application-id", APPLICATION_ID,
+ "flags", G_APPLICATION_NON_UNIQUE,
+ nullptr));
+}
diff --git a/linux/my_application.h b/linux/my_application.h
new file mode 100644
index 0000000..72271d5
--- /dev/null
+++ b/linux/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+ GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif // FLUTTER_MY_APPLICATION_H_
diff --git a/pubspec.lock b/pubspec.lock
new file mode 100644
index 0000000..2d38fbb
--- /dev/null
+++ b/pubspec.lock
@@ -0,0 +1,309 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: "20071638cbe4e5964a427cfa0e86dce55d060bc7d82d56f3554095d7239a8765"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.2"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.11.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.17.2"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ fixnum:
+ dependency: transitive
+ description:
+ name: fixnum
+ sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.4"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ googleapis_auth:
+ dependency: transitive
+ description:
+ name: googleapis_auth
+ sha256: af7c3a3edf9d0de2e1e0a77e994fae0a581c525fa7012af4fa0d4a52ed9484da
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.1"
+ grpc:
+ dependency: "direct main"
+ description:
+ name: grpc
+ sha256: e93ee3bce45c134bf44e9728119102358c7cd69de7832d9a874e2e74eb8cab40
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.4"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ http2:
+ dependency: transitive
+ description:
+ name: http2
+ sha256: "38db0c4aa9f1cd238a5d2e86aa0cc7cc91c77e0c6c94ba64bbe85e4ff732a952"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.2"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.7"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.16"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.1"
+ nested:
+ dependency: transitive
+ description:
+ name: nested
+ sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.8.3"
+ pointycastle:
+ dependency: transitive
+ description:
+ name: pointycastle
+ sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.7.3"
+ protobuf:
+ dependency: "direct main"
+ description:
+ name: protobuf
+ sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ provider:
+ dependency: "direct main"
+ description:
+ name: provider
+ sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.5"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.0"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.0"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.2"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.1.4-beta"
+sdks:
+ dart: ">=3.1.0-185.0.dev <4.0.0"
+ flutter: ">=1.16.0"
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..2dfd425
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,27 @@
+name: flutter_voiceassistant
+description: Offline voice assistant app designed for Automotive Grade Linux (AGL).
+
+version: 0.0.1+1
+
+environment:
+ sdk: '>=2.18.0 <3.0.0'
+
+dependencies:
+ flutter:
+ sdk: flutter
+
+ provider: ^6.0.0
+ grpc: ^3.1.0
+ protobuf: ^2.1.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ flutter_lints: ^1.0.0
+
+flutter:
+ uses-material-design: true
+ assets:
+ - assets/agl_logo.png
+ - assets/config.json \ No newline at end of file