From 8417e9daeecbdb3847de401b0fcc6304d246a787 Mon Sep 17 00:00:00 2001 From: Malik Talha Date: Mon, 23 Oct 2023 02:00:01 +0500 Subject: 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 Change-Id: Ic4a382c1cdb78f1a79f985e3d37ce2fb06c53203 --- .gitignore | 99 ++++ .gitreview | 5 + .metadata | 45 ++ LICENSE | 201 ++++++++ README.md | 16 + analysis_options.yaml | 10 + assets/agl_logo.png | Bin 0 -> 107245 bytes assets/config.json | 4 + lib/grpc/generated/voice_agent.pb.dart | 667 +++++++++++++++++++++++++++ lib/grpc/generated/voice_agent.pbenum.dart | 121 +++++ lib/grpc/generated/voice_agent.pbgrpc.dart | 136 ++++++ lib/grpc/generated/voice_agent.pbjson.dart | 251 ++++++++++ lib/grpc/voice_agent_client.dart | 71 +++ lib/main.dart | 77 ++++ lib/models/app_state.dart | 8 + lib/protos/voice_agent.proto | 83 ++++ lib/providers/service_status.dart | 12 + lib/screens/error_screen.dart | 62 +++ lib/screens/home_screen.dart | 398 ++++++++++++++++ lib/utils/app_config.dart | 26 ++ lib/widgets/assistant_mode_choice.dart | 202 ++++++++ lib/widgets/chat_section.dart | 128 +++++ lib/widgets/listen_wake_word_section.dart | 66 +++ lib/widgets/nlu_engine_choice.dart | 200 ++++++++ lib/widgets/record_command_button.dart | 66 +++ linux/.gitignore | 1 + linux/CMakeLists.txt | 139 ++++++ linux/flutter/CMakeLists.txt | 88 ++++ linux/flutter/generated_plugin_registrant.cc | 11 + linux/flutter/generated_plugin_registrant.h | 15 + linux/flutter/generated_plugins.cmake | 23 + linux/main.cc | 6 + linux/my_application.cc | 104 +++++ linux/my_application.h | 18 + pubspec.lock | 309 +++++++++++++ pubspec.yaml | 27 ++ 36 files changed, 3695 insertions(+) create mode 100644 .gitignore create mode 100644 .gitreview create mode 100644 .metadata create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 assets/agl_logo.png create mode 100644 assets/config.json create mode 100644 lib/grpc/generated/voice_agent.pb.dart create mode 100644 lib/grpc/generated/voice_agent.pbenum.dart create mode 100644 lib/grpc/generated/voice_agent.pbgrpc.dart create mode 100644 lib/grpc/generated/voice_agent.pbjson.dart create mode 100644 lib/grpc/voice_agent_client.dart create mode 100644 lib/main.dart create mode 100644 lib/models/app_state.dart create mode 100644 lib/protos/voice_agent.proto create mode 100644 lib/providers/service_status.dart create mode 100644 lib/screens/error_screen.dart create mode 100644 lib/screens/home_screen.dart create mode 100644 lib/utils/app_config.dart create mode 100644 lib/widgets/assistant_mode_choice.dart create mode 100644 lib/widgets/chat_section.dart create mode 100644 lib/widgets/listen_wake_word_section.dart create mode 100644 lib/widgets/nlu_engine_choice.dart create mode 100644 lib/widgets/record_command_button.dart create mode 100644 linux/.gitignore create mode 100644 linux/CMakeLists.txt create mode 100644 linux/flutter/CMakeLists.txt create mode 100644 linux/flutter/generated_plugin_registrant.cc create mode 100644 linux/flutter/generated_plugin_registrant.h create mode 100644 linux/flutter/generated_plugins.cmake create mode 100644 linux/main.cc create mode 100644 linux/my_application.cc create mode 100644 linux/my_application.h create mode 100644 pubspec.lock create mode 100644 pubspec.yaml 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 Binary files /dev/null and b/assets/agl_logo.png 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 createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static Empty getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(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 createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ServiceStatus getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(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 createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static WakeWordStatus getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(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(1, _omitFieldNames ? '' : 'action', $pb.PbFieldType.OE, + defaultOrMaker: RecordAction.START, + valueOf: RecordAction.valueOf, + enumValues: RecordAction.values) + ..e(2, _omitFieldNames ? '' : 'nluModel', $pb.PbFieldType.OE, + defaultOrMaker: NLUModel.SNIPS, + valueOf: NLUModel.valueOf, + enumValues: NLUModel.values) + ..e(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 createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static RecognizeControl getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(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 createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static IntentSlot getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(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? 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( + 3, _omitFieldNames ? '' : 'intentSlots', $pb.PbFieldType.PM, + subBuilder: IntentSlot.create) + ..aOS(4, _omitFieldNames ? '' : 'streamId') + ..e( + 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 createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static RecognizeResult getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(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 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? 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( + 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 createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ExecuteInput getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(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 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( + 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 createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ExecuteResult getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(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 values = [ + 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 values = [ + 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 values = [ + 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 values = [ + 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 values = [ + 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 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 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 recognizeVoiceCommand( + Stream 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 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 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 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( + 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 { + late AppConfig _config; // Store the config as an instance variable + final ScrollController _scrollController = ScrollController(); + List chatMessages = []; + StreamSubscription? _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(); + 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(); + + 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(); + 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(); + 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(); + 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 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 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 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(); + + 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( + 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( + 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(builder: (context, appState, _) { + return RecordCommandButton( + onRecordingStateChanged: (isRecording) { + changeCommandRecordingState(context, isRecording); + }, + ); + }), + ) + else + Center( + child: Consumer( + 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 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 { +// @override +// Widget build(BuildContext context) { +// final appState = context.watch(); // Watch the app state + +// return SegmentedButton( +// segments: const >[ +// ButtonSegment( +// value: AssistantMode.wakeWord, +// label: Text('Wake Word'), +// icon: Icon(Icons.graphic_eq)), +// ButtonSegment( +// value: AssistantMode.manual, +// label: Text('Manual'), +// icon: Icon(Icons.graphic_eq)), +// ], +// selected: { +// appState.isWakeWordMode ? AssistantMode.wakeWord : AssistantMode.manual +// }, // Use app state +// onSelectionChanged: (Set 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( +// width: 0, // Remove border width +// color: Colors.transparent, // Make border transparent +// ), +// ), +// backgroundColor: MaterialStateProperty.resolveWith( +// (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((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 { + 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: [ + 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 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 + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _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 createState() => _NLUEngineChoiceState(); +// } + +// class _NLUEngineChoiceState extends State { +// NLUEngine nluView = NLUEngine.snips; + +// @override +// Widget build(BuildContext context) { +// return SegmentedButton( +// segments: const >[ +// ButtonSegment( +// value: NLUEngine.snips, +// label: Text('Snips'), +// icon: Icon(Icons.settings_suggest)), +// ButtonSegment( +// value: NLUEngine.rasa, +// label: Text('RASA'), +// icon: Icon(Icons.settings_suggest)), +// ], +// selected: {nluView}, +// onSelectionChanged: (Set 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( +// width: 0, // Remove border width +// color: Colors.transparent, // Make border transparent +// ), +// ), +// backgroundColor: MaterialStateProperty.resolveWith( +// (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((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 createState() => _NLUEngineChoiceState(); +} + +class _NLUEngineChoiceState extends State { + 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: [ + 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 onRecordingStateChanged; + + RecordCommandButton({required this.onRecordingStateChanged}); + + @override + RecordCommandButtonState createState() => RecordCommandButtonState(); +} + +class RecordCommandButtonState extends State { + 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 "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>: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 + +// 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 $) + 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 +#ifdef GDK_WINDOWING_X11 +#include +#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 + +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 -- cgit 1.2.3-korg