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 --- 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 +++ 17 files changed, 2574 insertions(+) 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 (limited to 'lib') 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, + ), + ), + ], + ); + } +} -- cgit 1.2.3-korg