From f870bbe3c49d421ff8ea561752b3b0a38ad04e96 Mon Sep 17 00:00:00 2001 From: Anuj Solanki Date: Sun, 29 Sep 2024 21:31:03 +0530 Subject: [PATCH] Integrate voice assistant into flutter-ics-homescreen MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - Implement voice-agent client to connect with agl-service-voiceagent for command execution, wake word detection. - ⁠Add a setting tile on the settings page for configuring voice assistant settings. - Add toggle buttons for wake word mode, online mode, overlay and speech-to-text model in the voice assistant settings. - Add a button on the homepage to start the voice assistant. - Update gRPC protos to retrieve online-mode status from the voice service. - Make online-mode tile conditional in voice-assistant settings, removing it from the UI if not enabled in the service. - Automatically hide the overlay 3 seconds after command execution. Bug-AGL: SPEC-5200 Change-Id: I4efaaf16ebc570b28816dc7203364efe2b658c2e Signed-off-by: Anuj Solanki --- animations/LoadingAnimation.json | 1 + assets/VoiceAssistantActive.svg | 56 ++ assets/VoiceAssistantBottomSheet.svg | 9 + assets/VoiceAssistantBottomSheetBg.png | Bin 0 -> 46359 bytes assets/VoiceAssistantEnabled.svg | 57 ++ assets/VoiceAssistantPressed.svg | 33 + assets/VoiceControlButton.svg | 62 ++ lib/data/data_providers/app_config_provider.dart | 51 +- lib/data/data_providers/app_provider.dart | 17 +- lib/data/data_providers/voice_agent_client.dart | 312 ++++++++ .../data_providers/voice_assistant_notifier.dart | 148 ++++ lib/data/models/voice_assistant_state.dart | 104 +++ .../common_widget/voice_assistant_button.dart | 214 +++++ lib/presentation/router/routes/routes.dart | 6 + lib/presentation/screens/home/home.dart | 11 + .../voice_assistant/voice_assistant_screen.dart | 28 + .../widgets/stt_model/stt_model_screen.dart | 120 +++ .../widgets/voice_assistant_content.dart | 251 ++++++ .../voice_assistant_settings_list_tile.dart | 111 +++ .../widgets/voice_assistant_tile.dart | 102 +++ .../screens/settings/widgets/settings_content.dart | 9 + .../src/generated/voice_agent/voice_agent.pb.dart | 880 +++++++++++++++++++++ .../generated/voice_agent/voice_agent.pbenum.dart | 138 ++++ .../generated/voice_agent/voice_agent.pbgrpc.dart | 167 ++++ .../generated/voice_agent/voice_agent.pbjson.dart | 326 ++++++++ protos/lib/val_api.dart | 5 + protos/protos/voice_agent/voice_agent.proto | 120 +++ 27 files changed, 3334 insertions(+), 4 deletions(-) create mode 100644 animations/LoadingAnimation.json create mode 100644 assets/VoiceAssistantActive.svg create mode 100644 assets/VoiceAssistantBottomSheet.svg create mode 100644 assets/VoiceAssistantBottomSheetBg.png create mode 100644 assets/VoiceAssistantEnabled.svg create mode 100644 assets/VoiceAssistantPressed.svg create mode 100644 assets/VoiceControlButton.svg create mode 100644 lib/data/data_providers/voice_agent_client.dart create mode 100644 lib/data/data_providers/voice_assistant_notifier.dart create mode 100644 lib/data/models/voice_assistant_state.dart create mode 100644 lib/presentation/common_widget/voice_assistant_button.dart create mode 100644 lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart create mode 100644 lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart create mode 100644 lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart create mode 100644 lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart create mode 100644 lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart create mode 100644 protos/lib/src/generated/voice_agent/voice_agent.pb.dart create mode 100644 protos/lib/src/generated/voice_agent/voice_agent.pbenum.dart create mode 100644 protos/lib/src/generated/voice_agent/voice_agent.pbgrpc.dart create mode 100644 protos/lib/src/generated/voice_agent/voice_agent.pbjson.dart create mode 100644 protos/protos/voice_agent/voice_agent.proto diff --git a/animations/LoadingAnimation.json b/animations/LoadingAnimation.json new file mode 100644 index 0000000..b8a7d49 --- /dev/null +++ b/animations/LoadingAnimation.json @@ -0,0 +1 @@ +{"nm":"LoadingAnimation","ddd":0,"h":40,"w":277,"meta":{"g":"LottieFiles Figma v67"},"layers":[{"ty":0,"nm":"voiceAnimation","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[138.5,20]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[138.5,20]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"w":277,"h":40,"refId":"1","ind":1}],"v":"5.7.0","fr":60,"op":78.18,"ip":0,"assets":[{"nm":"[Asset] voiceAnimation","id":"1","layers":[{"ty":0,"nm":"Frame 3","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[203.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[203.5,20],"t":78},{"s":[203.5,20],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"w":277,"h":40,"refId":"2","ind":1},{"ty":0,"nm":"Frame 2","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[143.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[143.5,20],"t":78},{"s":[143.5,20],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"w":277,"h":40,"refId":"3","ind":2},{"ty":0,"nm":"Frame 1","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[83.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[83.5,20],"t":78},{"s":[83.5,20],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"w":277,"h":40,"refId":"4","ind":3},{"ty":4,"nm":"voiceAnimation Bg","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[138.5,20],"t":78},{"s":[138.5,20],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[138.5,20],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[138.5,20],"t":78},{"s":[138.5,20],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":78},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[277,0],[277,40],[0,40],[0,0]]}],"t":84}]}}],"ind":4}]},{"nm":"[Asset] Frame 3","id":"2","layers":[{"ty":4,"nm":"circle3","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[10,10],"t":78},{"s":[10,10],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15.5,15],"t":78},{"s":[15.5,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"ef":[{"ty":25,"nm":"","en":1,"ef":[{"ty":2,"nm":"color","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7687,0.1138,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.7687,0.1138,1],"t":78},{"s":[0.7687,0.1138,1],"t":84}]}},{"ty":0,"nm":"opacity","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[256],"t":78},{"s":[256],"t":84}]}},{"ty":1,"nm":"angle","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[90],"t":78},{"s":[90],"t":84}]}},{"ty":0,"nm":"distance","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]}},{"ty":0,"nm":"blur","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[8],"t":78},{"s":[8],"t":84}]}}]}],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-8.28],[0,0],[8.28,0],[0,0],[0,8.28],[0,0],[-8.28,0],[0,0]],"o":[[0,0],[0,8.28],[0,0],[-8.28,0],[0,0],[0,-8.28],[0,0],[8.28,0]],"v":[[30,15],[30,15],[15,30],[15,30],[0,15],[0,15],[15,0],[15,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":78},{"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":84}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.7693,0.1125,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.7693,0.1125,1],"t":78},{"s":[0.7693,0.1125,1],"t":84}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}}],"ind":1},{"ty":4,"nm":"Frame 3 Bg","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":78},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":84}]}}],"ind":2}]},{"nm":"[Asset] Frame 2","id":"3","layers":[{"ty":4,"nm":"circle2","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[10,10],"t":78},{"s":[10,10],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15.5,15],"t":78},{"s":[15.5,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"ef":[{"ty":25,"nm":"","en":1,"ef":[{"ty":2,"nm":"color","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2314,0.3569,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.2314,0.3569,1],"t":78},{"s":[0.2314,0.3569,1],"t":84}]}},{"ty":0,"nm":"opacity","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[256],"t":78},{"s":[256],"t":84}]}},{"ty":1,"nm":"angle","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[90],"t":78},{"s":[90],"t":84}]}},{"ty":0,"nm":"distance","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]}},{"ty":0,"nm":"blur","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[8],"t":78},{"s":[8],"t":84}]}}]}],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-8.28],[0,0],[8.28,0],[0,0],[0,8.28],[0,0],[-8.28,0],[0,0]],"o":[[0,0],[0,8.28],[0,0],[-8.28,0],[0,0],[0,-8.28],[0,0],[8.28,0]],"v":[[30,15],[30,15],[15,30],[15,30],[0,15],[0,15],[15,0],[15,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":78},{"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":84}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.2334,0.356,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.2334,0.356,1],"t":78},{"s":[0.2334,0.356,1],"t":84}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}}],"ind":1},{"ty":4,"nm":"Frame 2 Bg","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":78},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":84}]}}],"ind":2}]},{"nm":"[Asset] Frame 1","id":"4","layers":[{"ty":4,"nm":"circle1","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[9,9],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[10,10],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[9,9],"t":78},{"s":[9,9],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15.5,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15.5,15],"t":78},{"s":[15.5,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"ef":[{"ty":25,"nm":"","en":1,"ef":[{"ty":2,"nm":"color","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0236,0.9412,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.0236,0.9412,1],"t":78},{"s":[0.0236,0.9412,1],"t":84}]}},{"ty":0,"nm":"opacity","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[256],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[256],"t":78},{"s":[256],"t":84}]}},{"ty":1,"nm":"angle","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[90],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[90],"t":78},{"s":[90],"t":84}]}},{"ty":0,"nm":"distance","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]}},{"ty":0,"nm":"blur","v":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[8],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[8],"t":78},{"s":[8],"t":84}]}}]}],"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-8.28],[0,0],[8.28,0],[0,0],[0,8.28],[0,0],[-8.28,0],[0,0]],"o":[[0,0],[0,8.28],[0,0],[-8.28,0],[0,0],[0,-8.28],[0,0],[8.28,0]],"v":[[30,15],[30,15],[15,30],[15,30],[0,15],[0,15],[15,0],[15,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,-5.52],[0,0],[5.52,0],[0,0],[0,5.52],[0,0],[-5.52,0],[0,0]],"o":[[0,0],[0,5.52],[0,0],[-5.52,0],[0,0],[0,-5.52],[0,0],[5.52,0]],"v":[[20,10],[20,10],[10,20],[10,20],[0,10],[0,10],[10,0],[10,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":78},{"s":[{"c":true,"i":[[0,-4.97],[0,0],[4.97,0],[0,0],[0,4.97],[0,0],[-4.97,0],[0,0]],"o":[[0,0],[0,4.97],[0,0],[-4.97,0],[0,0],[0,-4.97],[0,0],[4.97,0]],"v":[[18,9],[18,9],[9,18],[9,18],[0,9],[0,9],[9,0],[9,0]]}],"t":84}]}},{"ty":"fl","bm":0,"hd":false,"nm":"","c":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0.0251,0.9415,1],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0.0251,0.9415,1],"t":78},{"s":[0.0251,0.9415,1],"t":84}]},"r":1,"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}}],"ind":1},{"ty":4,"nm":"Frame 1 Bg","sr":1,"st":0,"op":79.18,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"s":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100,100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100,100],"t":78},{"s":[100,100],"t":84}]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[15,15],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[15,15],"t":78},{"s":[15,15],"t":84}]},"r":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[0],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[0],"t":78},{"s":[0],"t":84}]},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[100],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[100],"t":78},{"s":[100],"t":84}]}},"shapes":[{"ty":"sh","bm":0,"hd":false,"nm":"","d":1,"ks":{"a":1,"k":[{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":0},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":6},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":24},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":42},{"o":{"x":0.33,"y":1},"i":{"x":0.68,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":60},{"o":{"x":0,"y":0},"i":{"x":1,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":78},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[30,0],[30,30],[0,30],[0,0]]}],"t":84}]}}],"ind":2}]}]} diff --git a/assets/VoiceAssistantActive.svg b/assets/VoiceAssistantActive.svg new file mode 100644 index 0000000..3589294 --- /dev/null +++ b/assets/VoiceAssistantActive.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/VoiceAssistantBottomSheet.svg b/assets/VoiceAssistantBottomSheet.svg new file mode 100644 index 0000000..cde9fbc --- /dev/null +++ b/assets/VoiceAssistantBottomSheet.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/VoiceAssistantBottomSheetBg.png b/assets/VoiceAssistantBottomSheetBg.png new file mode 100644 index 0000000000000000000000000000000000000000..059864a0a9d1f1a87ef6e552c320a1f312620b73 GIT binary patch literal 46359 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 literal 0 HcmV?d00001 diff --git a/assets/VoiceAssistantEnabled.svg b/assets/VoiceAssistantEnabled.svg new file mode 100644 index 0000000..6b5a83d --- /dev/null +++ b/assets/VoiceAssistantEnabled.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/VoiceAssistantPressed.svg b/assets/VoiceAssistantPressed.svg new file mode 100644 index 0000000..7ac4e33 --- /dev/null +++ b/assets/VoiceAssistantPressed.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/VoiceControlButton.svg b/assets/VoiceControlButton.svg new file mode 100644 index 0000000..bbd2757 --- /dev/null +++ b/assets/VoiceControlButton.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/data/data_providers/app_config_provider.dart b/lib/data/data_providers/app_config_provider.dart index b82eb54..9e187a9 100644 --- a/lib/data/data_providers/app_config_provider.dart +++ b/lib/data/data_providers/app_config_provider.dart @@ -88,6 +88,21 @@ class MpdConfig { } } +class VoiceAgentConfig { + final String hostname; + final int port; + + static String defaultHostname = 'localhost'; + static int defaultPort = 51053; + + VoiceAgentConfig({required this.hostname,required this.port}); + + static VoiceAgentConfig defaultConfig() { + return VoiceAgentConfig( + hostname: VoiceAgentConfig.defaultHostname, port: VoiceAgentConfig.defaultPort); + } +} + class AppConfig { final bool disableBkgAnimation; final bool plainBackground; @@ -96,6 +111,7 @@ class AppConfig { final RadioConfig radioConfig; final StorageConfig storageConfig; final MpdConfig mpdConfig; + final VoiceAgentConfig voiceAgentConfig; static String configFilePath = '/etc/xdg/AGL/ics-homescreen.yaml'; @@ -106,7 +122,8 @@ class AppConfig { required this.kuksaConfig, required this.radioConfig, required this.storageConfig, - required this.mpdConfig}); + required this.mpdConfig, + required this.voiceAgentConfig}); static KuksaConfig parseKuksaConfig(YamlMap kuksaMap) { try { @@ -238,6 +255,25 @@ class AppConfig { return MpdConfig.defaultConfig(); } } + + static VoiceAgentConfig parseVoiceAgentConfig(YamlMap voiceAgentMap) { + try { + String hostname = VoiceAgentConfig.defaultHostname; + if (voiceAgentMap.containsKey('hostname')) { + hostname = voiceAgentMap['hostname']; + } + + int port = VoiceAgentConfig.defaultPort; + if (voiceAgentMap.containsKey('port')) { + port = voiceAgentMap['port']; + } + + return VoiceAgentConfig(hostname: hostname, port: port); + } catch (_) { + debugPrint("Invalid VoiceAgent configuration, using defaults"); + return VoiceAgentConfig.defaultConfig(); + } + } } final appConfigProvider = Provider((ref) { @@ -281,6 +317,13 @@ final appConfigProvider = Provider((ref) { mpdConfig = MpdConfig.defaultConfig(); } + VoiceAgentConfig voiceAgentConfig; + if(yamlMap.containsKey('voiceAgent')){ + voiceAgentConfig = AppConfig.parseVoiceAgentConfig(yamlMap['voiceAgent']); + } else { + voiceAgentConfig = VoiceAgentConfig.defaultConfig(); + } + bool disableBkgAnimation = disableBkgAnimationDefault; if (yamlMap.containsKey('disable-bg-animation')) { var value = yamlMap['disable-bg-animation']; @@ -312,7 +355,8 @@ final appConfigProvider = Provider((ref) { kuksaConfig: kuksaConfig, radioConfig: radioConfig, storageConfig: storageConfig, - mpdConfig: mpdConfig); + mpdConfig: mpdConfig, + voiceAgentConfig: voiceAgentConfig); } catch (_) { return AppConfig( disableBkgAnimation: false, @@ -321,6 +365,7 @@ final appConfigProvider = Provider((ref) { kuksaConfig: KuksaConfig.defaultConfig(), radioConfig: RadioConfig.defaultConfig(), storageConfig: StorageConfig.defaultConfig(), - mpdConfig: MpdConfig.defaultConfig()); + mpdConfig: MpdConfig.defaultConfig(), + voiceAgentConfig: VoiceAgentConfig.defaultConfig()); } }); diff --git a/lib/data/data_providers/app_provider.dart b/lib/data/data_providers/app_provider.dart index 0f7ed0c..64c0e47 100644 --- a/lib/data/data_providers/app_provider.dart +++ b/lib/data/data_providers/app_provider.dart @@ -16,10 +16,14 @@ import 'package:flutter_ics_homescreen/data/data_providers/radio_client.dart'; import 'package:flutter_ics_homescreen/data/data_providers/storage_client.dart'; import 'package:flutter_ics_homescreen/data/data_providers/mpd_client.dart'; import 'package:flutter_ics_homescreen/data/data_providers/play_controller.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/voice_agent_client.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/voice_assistant_notifier.dart'; import 'package:flutter_ics_homescreen/export.dart'; import 'package:flutter_ics_homescreen/data/models/users.dart'; +import '../models/voice_assistant_state.dart'; + enum AppState { home, dashboard, @@ -44,7 +48,9 @@ enum AppState { clock, date, time, - year + year, + voiceAssistant, + sttModel, } class AppStateNotifier extends Notifier { @@ -73,6 +79,11 @@ final valClientProvider = Provider((ref) { return ValClient(config: config, ref: ref); }); +final voiceAgentClientProvider = Provider((ref){ + VoiceAgentConfig config = ref.watch(appConfigProvider).voiceAgentConfig; + return VoiceAgentClient(config: config, ref: ref); +}); + final appLauncherProvider = Provider((ref) { return AppLauncher(ref: ref); }); @@ -154,3 +165,7 @@ final currentTimeProvider = StateNotifierProvider((ref) { return CurrentTimeNotifier(); }); + + +final voiceAssistantStateProvider = + NotifierProvider(VoiceAssistantStateNotifier.new); diff --git a/lib/data/data_providers/voice_agent_client.dart b/lib/data/data_providers/voice_agent_client.dart new file mode 100644 index 0000000..295e138 --- /dev/null +++ b/lib/data/data_providers/voice_agent_client.dart @@ -0,0 +1,312 @@ +import 'dart:async'; +import 'package:flutter_ics_homescreen/data/models/voice_assistant_state.dart'; +import 'package:protos/val_api.dart'; + +import '../../export.dart'; + +class VoiceAgentClient { + final VoiceAgentConfig config; + late ClientChannel _channel; + late VoiceAgentServiceClient _client; + final Ref ref; + StreamSubscription? _wakeWordStatusSubscription; + + VoiceAgentClient({required this.config,required this.ref}) { + // Initialize the client channel without connecting immediately + String host = config.hostname; + int port = config.port; + _channel = ClientChannel( + host, + port: port, + options: const ChannelOptions( + credentials: ChannelCredentials.insecure(), + ), + ); + debugPrint("Connecting to Voice Assistant"); + _client = VoiceAgentServiceClient(_channel); + + } + + Future checkServiceStatus() async { + final empty = Empty(); + try { + final response = await _client.checkServiceStatus(empty); + return response; + } catch (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) { + // Handle the error gracefully, such as returning a default status + return const Stream.empty(); // An empty stream as a placeholder + } + } + + Future recognizeVoiceCommand( + Stream controlStream) async { + try { + final response = await _client.recognizeVoiceCommand(controlStream); + return response; + } catch (e) { + // Handle the error gracefully, such as returning a default RecognizeResult + return RecognizeResult()..status = RecognizeStatusType.REC_ERROR; + } + } + + Future recognizeTextCommandGrpc( + RecognizeTextControl controlInput) async { + try { + final response = await _client.recognizeTextCommand(controlInput); + return response; + } catch (e) { + // Handle the error gracefully, such as returning a default RecognizeResult + return RecognizeResult()..status = RecognizeStatusType.REC_ERROR; + } + } + + Future executeCommandGrpc(ExecuteInput input) async { + try { + final response = await _client.executeCommand(input); + return response; + } catch (e) { + // Handle the error gracefully, such as returning an error status + return ExecuteResult()..status = ExecuteStatusType.EXEC_ERROR; + } + } + + Future shutdown() async { + // await _channel.shutdown(); + } + + // Grpc helper methods + Future startWakeWordDetection() async { + // Capture the state before any async operations + _wakeWordStatusSubscription?.cancel(); + final isWakeWordModeActive = ref.read(voiceAssistantStateProvider.select((value) => value.isWakeWordMode)); + + if (isWakeWordModeActive) { + debugPrint("Wake Word Detection Started"); + } else { + debugPrint("Wake Word Detection Stopped"); + return; + } + _wakeWordStatusSubscription = detectWakeWord().listen( + (response) async { + if (response.status) { + await startVoiceAssistant(); + // Wait for 2-3 seconds and then restart wake word detection + await Future.delayed(const Duration(seconds: 2)); + startWakeWordDetection(); + } + if(!ref.read(voiceAssistantStateProvider.select((value) => value.isWakeWordMode))){ + _wakeWordStatusSubscription?.cancel(); + return; + } + }, + onError: (error) { + }, + cancelOnError: true, + ); + } + + Future startRecording() async { + String streamId = ""; + try { + // Create a RecognizeControl message to start recording + final controlMessage = RecognizeVoiceControl() + ..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 recognizeVoiceCommand(controlStream); + + streamId = response.streamId; + } catch (e) { + } + return streamId; + } + + Future stopRecording( + String streamId, String nluModel, String stt,bool isOnlineMode) async { + + try { + NLUModel model = NLUModel.RASA; + if (nluModel == "snips") { + model = NLUModel.SNIPS; + } + STTFramework sttFramework = STTFramework.VOSK; + if (stt == "whisper") { + sttFramework = STTFramework.WHISPER; + } + OnlineMode onlineMode = OnlineMode.OFFLINE; + if (isOnlineMode) { + onlineMode = OnlineMode.ONLINE; + } + // Create a RecognizeControl message to stop recording + final controlMessage = RecognizeVoiceControl() + ..action = RecordAction.STOP + ..nluModel = model + ..streamId = + streamId // Use the same stream ID as when starting recording + ..recordMode = RecordMode.MANUAL + ..sttFramework = sttFramework + ..onlineMode = onlineMode; + + + // Create a Stream with the control message + final controlStream = Stream.fromIterable([controlMessage]); + + // Call the gRPC method to stop recording + final response = + await recognizeVoiceCommand(controlStream); + + // Process and store the result + if (response.status == RecognizeStatusType.REC_SUCCESS) { + } else if (response.status == RecognizeStatusType.INTENT_NOT_RECOGNIZED) { + final command = response.command; + debugPrint("Command is : $command"); + } + else { + debugPrint('Failed to process your voice command. Please try again.'); + } + await shutdown(); + return response; + } catch (e) { + // addChatMessage(/**/'Failed to process your voice command. Please try again.'); + await shutdown(); + return RecognizeResult()..status = RecognizeStatusType.REC_ERROR; + } + // await voiceAgentClient.shutdown(); + } + + Future recognizeTextCommand(String command, String nluModel) async { + debugPrint("Recognizing Text Command: $command"); + try { + NLUModel model = NLUModel.RASA; + if (nluModel == "snips") { + model = NLUModel.SNIPS; + } + // Create a RecognizeControl message to stop recording + final controlMessage = RecognizeTextControl() + ..textCommand = command + ..nluModel = model; + + // Call the gRPC method to stop recording + final response = + await recognizeTextCommandGrpc(controlMessage); + debugPrint("Response is : $response"); + + // Process and store the result + if (response.status == RecognizeStatusType.REC_SUCCESS) { + // Do nothing + } else if (response.status == RecognizeStatusType.INTENT_NOT_RECOGNIZED) { + final command = response.command; + debugPrint("Command is : $command"); + } else { + debugPrint('Failed to process your voice command. Please try again.'); + } + return response; + } catch (e) { + return RecognizeResult()..status = RecognizeStatusType.REC_ERROR; + } + } + + Future executeCommand(RecognizeResult response) async { + 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 executeCommandGrpc(executeInput); + + // Handle the response as needed + if (execResponse.status == ExecuteStatusType.EXEC_SUCCESS) { + final commandResponse = execResponse.response; + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(commandResponse); + debugPrint("Command Response is : $commandResponse"); + } else if (execResponse.status == ExecuteStatusType.KUKSA_CONN_ERROR) { + final commandResponse = execResponse.response; + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(commandResponse); + } else { + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse("Sorry, I couldn't execute your command. Please try again."); + } + } catch (e) { + } + await shutdown(); + } + + + Future disableOverlay() async{ + await Future.delayed(Duration(seconds: 3)); + ref.read(voiceAssistantStateProvider.notifier).toggleShowOverlay(false); + } + + Future startVoiceAssistant()async { + ref.read(voiceAssistantStateProvider.notifier).updateCommand(null); + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(null); + + SttModel stt = ref.read(voiceAssistantStateProvider.select((value)=>value.sttModel)); + bool isOnlineMode = ref.read(voiceAssistantStateProvider.select((value)=>value.isOnlineMode)); + String nluModel = "snips"; + String sttModel = "whisper"; + if(stt == SttModel.vosk){ + sttModel = "vosk"; + } + bool isOverlayEnabled = ref.read(voiceAssistantStateProvider.select((value)=>value.voiceAssistantOverlay)); + bool overlayState = ref.read(voiceAssistantStateProvider.select((value)=>value.showOverLay)); + + String streamId = await startRecording(); + if (streamId.isNotEmpty) { + debugPrint('Recording started. Please speak your command.'); + if(isOverlayEnabled){ + if(!overlayState){ + ref.read(voiceAssistantStateProvider.notifier).toggleShowOverlay(true); + } + } + + ref.read(voiceAssistantStateProvider.notifier).updateButtonPressed(true); + ref.read(voiceAssistantStateProvider.notifier).updateIsRecording(); + ref.read(voiceAssistantStateProvider.notifier).updateIsCommandProcessing(false); + + // wait for the recording time + await Future.delayed(Duration(seconds: ref.watch(voiceAssistantStateProvider.select((value)=>value.recordingTime)))); + + ref.read(voiceAssistantStateProvider.notifier).updateIsRecording(); + ref.read(voiceAssistantStateProvider.notifier).updateIsCommandProcessing(true); + + // stop the recording and process the command + RecognizeResult recognizeResult = await stopRecording(streamId, nluModel, sttModel,isOnlineMode); + + ref.read(voiceAssistantStateProvider.notifier).updateCommand(recognizeResult.command); + debugPrint('Recording stopped. Processing the command...'); + + // Execute the command + await executeCommand(recognizeResult); + + ref.read(voiceAssistantStateProvider.notifier).updateIsCommandProcessing(false); + ref.read(voiceAssistantStateProvider.notifier).updateButtonPressed(false); + ref.read(voiceAssistantStateProvider.notifier).updateCommand(null); + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(null); + disableOverlay(); + + } else { + debugPrint('Failed to start recording. Please try again.'); + } + + } + + +} diff --git a/lib/data/data_providers/voice_assistant_notifier.dart b/lib/data/data_providers/voice_assistant_notifier.dart new file mode 100644 index 0000000..0bc681a --- /dev/null +++ b/lib/data/data_providers/voice_assistant_notifier.dart @@ -0,0 +1,148 @@ +import 'package:protos/val_api.dart'; + +import '../../export.dart'; +import '../models/voice_assistant_state.dart'; + + +class VoiceAssistantStateNotifier extends Notifier{ + @override + VoiceAssistantState build() { + return const VoiceAssistantState.initial(); + } + + void updateVoiceAssistantState(VoiceAssistantState newState){ + state = newState; + } + + void updateVoiceAssistantStateWith({ + bool? isWakeWordMode, + bool? isVoiceAssistantEnable, + bool? voiceAssistantOverlay, + bool? isOnlineMode, + bool? isOnlineModeAvailable, + String? wakeWord, + SttModel? sttModel, + String? streamId, + bool? isCommandProcessing, + String? commandProcessingText, + int? recordingTime, + bool? buttonPressed, + bool? isRecording, + String? command, + String? commandResponse, + bool? isWakeWordDetected, + bool? showOverLay, + }){ + state = state.copyWith( + isWakeWordMode: isWakeWordMode, + isVoiceAssistantEnable: isVoiceAssistantEnable, + voiceAssistantOverlay: voiceAssistantOverlay, + isOnlineMode: isOnlineMode, + isOnlineModeAvailable: isOnlineModeAvailable, + wakeWord: wakeWord, + sttModel: sttModel, + streamId: streamId, + isCommandProcessing: isCommandProcessing, + commandProcessingText: commandProcessingText, + recordingTime: recordingTime, + buttonPressed: buttonPressed, + isRecording: isRecording, + command: command, + commandResponse: commandResponse, + isWakeWordDetected: isWakeWordDetected, + showOverLay: showOverLay, + ); + } + + void resetToDefaults(){ + state = const VoiceAssistantState.initial(); + } + + void updateWakeWordDetected(bool isWakeWordDetected){ + state = state.copyWith(isWakeWordDetected: isWakeWordDetected); + } + + void toggleShowOverlay(bool value){ + state = state.copyWith(showOverLay: value); + } + + bool toggleWakeWordMode(){ + state = state.copyWith(isWakeWordMode: !state.isWakeWordMode); + return state.isWakeWordMode; + } + + Future toggleVoiceAssistant(ServiceStatus status) async { + bool prevState = state.isVoiceAssistantEnable; + if(!prevState){ + if(status.status){ + state = state.copyWith(isVoiceAssistantEnable: !state.isVoiceAssistantEnable); + state = state.copyWith(wakeWord: status.wakeWord); + state = state.copyWith(isOnlineModeAvailable: status.onlineMode); + } + else{ + debugPrint("Failed to start the Voice Assistant"); + } + } + else{ + state = state.copyWith(isVoiceAssistantEnable: !state.isVoiceAssistantEnable); + if(state.isWakeWordMode){ + state = state.copyWith(isWakeWordMode: false); + } + } + } + + void toggleVoiceAssistantOverlay(){ + state = state.copyWith(voiceAssistantOverlay: !state.voiceAssistantOverlay); + } + + void toggleOnlineMode(){ + state = state.copyWith(isOnlineMode: !state.isOnlineMode); + } + + void updateWakeWord(String wakeWord){ + state = state.copyWith(wakeWord: wakeWord); + } + + void updateSttModel(SttModel sttModel){ + state = state.copyWith(sttModel: sttModel); + } + + void updateStreamId(String streamId){ + state = state.copyWith(streamId: streamId); + } + + void updateIsCommandProcessing(bool isCommandProcessing){ + state = state.copyWith(isCommandProcessing: isCommandProcessing); + } + + void updateCommandProcessingText(String commandProcessingText){ + state = state.copyWith(commandProcessingText: commandProcessingText); + } + + void updateRecordingTime(int recordingTime){ + state = state.copyWith(recordingTime: recordingTime); + } + + void updateIsRecording(){ + state = state.copyWith(isRecording: !state.isRecording); + } + + void updateCommand(String? command){ + state = state.copyWith(command: command); + } + + void updateCommandResponse(String? commandResponse){ + state = state.copyWith(commandResponse: commandResponse); + } + + + bool toggleButtonPressed(){ + bool prevState = state.buttonPressed; + state = state.copyWith(buttonPressed: !state.buttonPressed); + return !prevState; + } + + void updateButtonPressed(bool buttonPressed){ + state = state.copyWith(buttonPressed: buttonPressed); + } +} \ No newline at end of file diff --git a/lib/data/models/voice_assistant_state.dart b/lib/data/models/voice_assistant_state.dart new file mode 100644 index 0000000..f898dd5 --- /dev/null +++ b/lib/data/models/voice_assistant_state.dart @@ -0,0 +1,104 @@ +enum SttModel { + whisper, + vosk +} + +class VoiceAssistantState{ + final bool isWakeWordMode; + final bool isVoiceAssistantEnable; + final bool voiceAssistantOverlay; + final bool isOnlineMode; + final bool isOnlineModeAvailable; + final String wakeWord; + final SttModel sttModel; + final String streamId; + final bool isCommandProcessing; + final String commandProcessingText; + final int recordingTime; + final bool buttonPressed; + final bool isRecording; + final String command; + final String commandResponse; + final bool isWakeWordDetected; + final bool showOverLay; + + + const VoiceAssistantState({ + required this.isWakeWordMode, + required this.isVoiceAssistantEnable, + required this.voiceAssistantOverlay, + required this.isOnlineMode, + required this.isOnlineModeAvailable, + required this.wakeWord, + required this.sttModel, + required this.streamId, + required this.isCommandProcessing, + required this.commandProcessingText, + required this.recordingTime, + required this.buttonPressed, + required this.isRecording, + required this.command, + required this.commandResponse, + required this.isWakeWordDetected, + required this.showOverLay, + }); + + const VoiceAssistantState.initial() + : wakeWord = "hello auto", + sttModel = SttModel.whisper, + streamId = "", + isWakeWordMode = false, + isVoiceAssistantEnable = false, + voiceAssistantOverlay = false, + isOnlineMode = false, + isOnlineModeAvailable = false, + isCommandProcessing = false, + commandProcessingText = "Processing...", + recordingTime = 4, + buttonPressed = false, + isRecording = false, + command = "", + commandResponse = "", + isWakeWordDetected = false, + showOverLay = false; + + VoiceAssistantState copyWith({ + bool? isWakeWordMode, + bool? isVoiceAssistantEnable, + bool? voiceAssistantOverlay, + bool? isOnlineMode, + bool? isOnlineModeAvailable, + String? wakeWord, + SttModel? sttModel, + String? streamId, + bool? isCommandProcessing, + String? commandProcessingText, + int? recordingTime, + bool? buttonPressed, + bool? isRecording, + String? command, + String? commandResponse, + bool? isWakeWordDetected, + bool? showOverLay, + }) { + return VoiceAssistantState( + isVoiceAssistantEnable : isVoiceAssistantEnable ?? this.isVoiceAssistantEnable, + isWakeWordMode : isWakeWordMode ?? this.isWakeWordMode, + voiceAssistantOverlay : voiceAssistantOverlay ?? this.voiceAssistantOverlay, + isOnlineMode : isOnlineMode ?? this.isOnlineMode, + isOnlineModeAvailable : isOnlineModeAvailable ?? this.isOnlineModeAvailable, + wakeWord : wakeWord ?? this.wakeWord, + sttModel : sttModel ?? this.sttModel, + streamId : streamId ?? this.streamId, + isCommandProcessing : isCommandProcessing ?? this.isCommandProcessing, + commandProcessingText : commandProcessingText ?? this.commandProcessingText, + recordingTime : recordingTime ?? this.recordingTime, + buttonPressed : buttonPressed ?? this.buttonPressed, + isRecording : isRecording ?? this.isRecording, + command : command ?? this.command, + commandResponse : commandResponse ?? this.commandResponse, + isWakeWordDetected: isWakeWordDetected ?? this.isWakeWordDetected, + showOverLay: showOverLay ?? this.showOverLay, + ); + } +} \ No newline at end of file diff --git a/lib/presentation/common_widget/voice_assistant_button.dart b/lib/presentation/common_widget/voice_assistant_button.dart new file mode 100644 index 0000000..2a82a0a --- /dev/null +++ b/lib/presentation/common_widget/voice_assistant_button.dart @@ -0,0 +1,214 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class VoiceAssistantButton extends ConsumerStatefulWidget { + const VoiceAssistantButton({super.key}); + + @override + ConsumerState createState() => _VoiceAssistantButtonState(); +} + +class _VoiceAssistantButtonState extends ConsumerState with SingleTickerProviderStateMixin { + bool _showOverlay = false; + late AnimationController _animationController; + late Animation _pulseAnimation; + int overlayLock = 0; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 700), + )..stop(); // Stop the animation initially + + _pulseAnimation = Tween(begin: 1.0, end: 1.05).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + void _onTap() { + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(""); + ref.read(voiceAssistantStateProvider.notifier).updateCommand(""); + bool state = ref.read(voiceAssistantStateProvider.notifier).toggleButtonPressed(); + if(state){ + var voiceAgentClient = ref.read(voiceAgentClientProvider); + voiceAgentClient.startVoiceAssistant(); + } + } + + void _showAssistantPopup(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return Consumer( + builder: (context, ref, child) { + final String? command = ref.watch(voiceAssistantStateProvider.select((value) => value.command)); + final String? commandResponse = ref.watch(voiceAssistantStateProvider.select((value) => value.commandResponse)); + final bool isRecording = ref.watch(voiceAssistantStateProvider.select((value)=>value.isRecording)); + final bool isProcessing = ref.watch(voiceAssistantStateProvider.select((value)=>value.isCommandProcessing)); + + if (isRecording) { + _animationController.repeat(reverse: true); + } else { + _animationController.stop(); + } + + return Container( + height: MediaQuery.of(context).size.height * 0.35, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/VoiceAssistantBottomSheetBg.png'), + fit: BoxFit.cover, + ), + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(30, 0, 40, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if(!isRecording && !isProcessing) + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + command ?? "No Command Detected", + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white70, + fontSize: 43, + fontWeight: FontWeight.w400, + ), + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height * 0.03 + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + commandResponse ?? "No Response", + textAlign: TextAlign.center, + style: const TextStyle( + color: Color.fromRGBO(41, 95, 248, 1), + fontSize: 43, + fontWeight: FontWeight.w800, + ), + ), + ), + + SizedBox( + height: MediaQuery.of(context).size.height * 0.02, + ), + ], + ), + + if(isRecording) + Column( + children: [ + const Text("Listening...", + style: TextStyle( + color: Colors.white70, + fontSize: 43, + fontWeight: FontWeight.w400, + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height*0.02, + ), + ScaleTransition( + scale: _pulseAnimation, // Apply the pulse animation here + child: SvgPicture.asset( + 'assets/VoiceControlButton.svg', + fit: BoxFit.cover, + semanticsLabel: 'Voice Assistant', + ), + ), + ], + ), + + if(!isRecording && isProcessing) + Column( + children: [ + const Text("Processing...", + style: TextStyle( + color: Colors.white70, + fontSize: 43, + fontWeight: FontWeight.w400, + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height*0.05, + ), + Lottie.asset( + 'animations/LoadingAnimation.json', + fit: BoxFit.cover, + repeat: true, + ), + ], + ), + SizedBox( + height: MediaQuery.of(context).size.height * 0.035, + ), + ], + ), + ), + ); + }, + ); + }, + ).whenComplete(() { + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(null); + ref.read(voiceAssistantStateProvider.notifier).updateCommand(null); + ref.read(voiceAssistantStateProvider.notifier).toggleShowOverlay(false); + overlayLock = 0; + }); + } + + @override + Widget build(BuildContext context) { + _showOverlay = ref.watch(voiceAssistantStateProvider.select((value) => value.showOverLay)); + + if(_showOverlay){ + WidgetsBinding.instance!.addPostFrameCallback((_) { + if(overlayLock == 0){ + overlayLock = 1; + _showAssistantPopup(context); + } + }); + } + else if(overlayLock == 1){ + overlayLock = 0; + Navigator.of(context).pop(); + } + + String svgPath = ref.watch(voiceAssistantStateProvider.select((value) => value.buttonPressed)) + ? 'assets/VoiceAssistantActive.svg' + : 'assets/VoiceAssistantEnabled.svg'; + + return Padding( + padding: const EdgeInsets.only(left: 8), + child: GestureDetector( + onTap: _onTap, + child: Container( + padding: EdgeInsets.zero, + child: SvgPicture.asset( + svgPath, + fit: BoxFit.cover, + semanticsLabel: 'Voice Assistant', + ), + ), + ), + ); + } +} diff --git a/lib/presentation/router/routes/routes.dart b/lib/presentation/router/routes/routes.dart index 328d495..24eab3a 100644 --- a/lib/presentation/router/routes/routes.dart +++ b/lib/presentation/router/routes/routes.dart @@ -3,6 +3,8 @@ import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_sc import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/date_time/time/time_screen.dart'; import '../../../../export.dart'; +import '../../screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart'; +import '../../screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart'; List> onGenerateAppViewPages( AppState state, @@ -57,5 +59,9 @@ List> onGenerateAppViewPages( return [TimePage.page()]; case AppState.year: return [SelectYearPage.page()]; + case AppState.voiceAssistant: + return [VoiceAssistantPage.page()]; + case AppState.sttModel: + return [STTModelPage.page()]; } } diff --git a/lib/presentation/screens/home/home.dart b/lib/presentation/screens/home/home.dart index 0ee52ac..6e3e119 100644 --- a/lib/presentation/screens/home/home.dart +++ b/lib/presentation/screens/home/home.dart @@ -1,4 +1,6 @@ import 'package:flutter_ics_homescreen/export.dart'; + +import '../../common_widget/voice_assistant_button.dart'; // import 'package:media_kit_video/media_kit_video.dart'; final bkgImageProvider = Provider((ref) { @@ -76,6 +78,15 @@ class HomeScreenState extends ConsumerState { height: 500, child: const VolumeFanControl()), ), + // Voice Assistant Button + if (appState != AppState.splash && ref.watch(voiceAssistantStateProvider.select((value)=>value.isVoiceAssistantEnable))) + Positioned( + top: MediaQuery.of(context).size.height * 0.82, + child: Container( + padding: const EdgeInsets.only(left: 8), + child: const VoiceAssistantButton() + ), + ), ], ), bottomNavigationBar: diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart new file mode 100644 index 0000000..e1f38ae --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart @@ -0,0 +1,28 @@ + +import 'package:flutter_ics_homescreen/export.dart'; +import 'widgets/voice_assistant_content.dart'; + +class VoiceAssistantPage extends ConsumerWidget{ + const VoiceAssistantPage({super.key}); + + static Page page() => const MaterialPage(child: VoiceAssistantPage()); + @override + Widget build(BuildContext context,WidgetRef ref) { + + return Scaffold( + body: Column( + children: [ + CommonTitle( + title: 'Voice Assistant', + hasBackButton: true, + onPressed: () { + ref.read(appProvider.notifier).back(); + }, + ), + Expanded(child: VoiceAssistantContent()), + ], + ), + ); + } +} + diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart new file mode 100644 index 0000000..614763d --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart @@ -0,0 +1,120 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +import '../../../../../../../data/models/voice_assistant_state.dart'; + +class STTModelPage extends ConsumerWidget { + const STTModelPage({super.key}); + + static Page page() => + const MaterialPage(child: STTModelPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + final SttModel sttModel = ref.watch(voiceAssistantStateProvider.select((value) => value.sttModel)); + + return Scaffold( + body: Column( + children: [ + CommonTitle( + title: 'Speech to Text Model', + hasBackButton: true, + onPressed: () { + context.flow().update((state) => AppState.voiceAssistant); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 144), + child: ListView( + children: [ + Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: sttModel == SttModel.whisper + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: sttModel == SttModel.whisper + ? [ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : [Colors.black, Colors.black12]), + ), + child: ListTile( + minVerticalPadding: 0.0, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 40.0), + leading: Text( + 'Whisper AI', + style: Theme.of(context).textTheme.titleMedium, + ), + trailing: sttModel == SttModel.whisper + ? const Icon( + Icons.done, + color: AGLDemoColors.periwinkleColor, + size: 48, + ) + : null, + onTap: () { + ref + .read(voiceAssistantStateProvider.notifier) + .updateSttModel(SttModel.whisper); + }), + ), + const SizedBox( + height: 8, + ), + Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: sttModel == SttModel.vosk + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: sttModel == SttModel.vosk + ? [ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : [Colors.black, Colors.black12]), + ), + child: ListTile( + minVerticalPadding: 0.0, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 40.0), + leading: Text( + 'Vosk', + style: Theme.of(context).textTheme.titleMedium, + ), + //title: Text(widget.title), + //enabled: isSwitchOn, + trailing: sttModel == SttModel.vosk + ? const Icon( + Icons.done, + color: AGLDemoColors.periwinkleColor, + size: 48, + ) + : null, + + onTap: () { + ref + .read(voiceAssistantStateProvider.notifier) + .updateSttModel(SttModel.vosk); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart new file mode 100644 index 0000000..924a219 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart @@ -0,0 +1,251 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart'; + +import '../../../../../../core/utils/helpers.dart'; +import '../../../../../../data/models/voice_assistant_state.dart'; + +@immutable +class VoiceAssistantContent extends ConsumerWidget { + VoiceAssistantContent({Key? key}) : super(key: key); + bool isWakeWordMode = false; + bool isVoiceAssistantOverlay = false; + bool isOnlineMode = false; + SttModel sttModel = SttModel.whisper; + + @override + Widget build(BuildContext context, WidgetRef ref) { + isWakeWordMode = + ref.watch(voiceAssistantStateProvider.select((value) => value.isWakeWordMode)); + isVoiceAssistantOverlay = + ref.watch(voiceAssistantStateProvider.select((value) => value.voiceAssistantOverlay)); + isOnlineMode = + ref.watch(voiceAssistantStateProvider.select((value) => value.isOnlineMode)); + sttModel = + ref.watch(voiceAssistantStateProvider.select((value) => value.sttModel)); + + final wakeWordCallback = () { + bool status = ref.read(voiceAssistantStateProvider.notifier).toggleWakeWordMode(); + if(status){ + var voiceAgentClient = ref.read(voiceAgentClientProvider); + voiceAgentClient.startWakeWordDetection(); + } + }; + + final voiceAssistantOverlayCallback = () { + ref.read(voiceAssistantStateProvider.notifier).toggleVoiceAssistantOverlay(); + }; + + final onlineModeCallback = () { + ref.read(voiceAssistantStateProvider.notifier).toggleOnlineMode(); + }; + + + return Column( + children: [ + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + children: [ + VoiceAssistantTile( + icon: Icons.insert_comment_outlined, + title: "Voice Assistant Overlay", + hasSwitch: true, + voidCallback: voiceAssistantOverlayCallback, + isSwitchOn: isVoiceAssistantOverlay + ), + if(ref.watch(voiceAssistantStateProvider.select((value) => value.isOnlineModeAvailable))) + VoiceAssistantTile( + icon: Icons.cloud_circle, + title: "Online Mode", + hasSwitch: true, + voidCallback: onlineModeCallback, + isSwitchOn: isOnlineMode + ), + VoiceAssistantTile( + icon: Icons.mic_none_outlined, + title: "Wake Word Mode", + hasSwitch: true, + voidCallback: wakeWordCallback, + isSwitchOn: isWakeWordMode + ), + if(ref.watch(voiceAssistantStateProvider.select((value) => value.isWakeWordMode))) + WakeWordTile(), + SttTile( + title: " Speech To Text", + sttName: sttModel==SttModel.whisper ? "Whisper AI" : "Vosk", + hasSwich: true, + voidCallback: () async { + context + .flow() + .update((next) => AppState.sttModel); + }), + ], + ) + ), + ], + ); + } +} + +class SttTile extends ConsumerStatefulWidget { + final IconData? icon; + final String title; + final String sttName; + final bool hasSwich; + final VoidCallback voidCallback; + final String? image; + const SttTile({ + Key? key, + this.icon, + required this.title, + required this.sttName, + required this.hasSwich, + required this.voidCallback, + this.image, + }) : super(key: key); + + @override + SttTileState createState() => SttTileState(); +} + +class SttTileState extends ConsumerState { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(vertical: 15), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0.3, 1], + colors: [Colors.black, Colors.black12]), + ), + //color: Color(0xFF0D113F), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 17, horizontal: 24), + leading: Icon( + Icons.transcribe_outlined, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + title: Text( + widget.title, + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + shadows: [ + Helpers.dropShadowRegular, + ], + fontSize: 40), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + widget.sttName, + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + shadows: [ + Helpers.dropShadowRegular, + ], + fontSize: 40, + ), + ), + const SizedBox( + width: 24, + ), + const Icon( + Icons.arrow_forward_ios, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + ], + ), + onTap: widget.voidCallback, + ), + ), + const SizedBox( + height: 8, + ) + ], + ); + } +} + + + +class WakeWordTile extends ConsumerStatefulWidget { + const WakeWordTile({Key? key}) : super(key: key); + + @override + WakeWordTileState createState() => WakeWordTileState(); +} + +class WakeWordTileState extends ConsumerState { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(vertical: 15), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0.3, 1], + colors: [Colors.black, Colors.black12]), + ), + //color: Color(0xFF0D113F), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 17, horizontal: 24), + leading: Icon( + Icons.mic_none_outlined, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + title: Text( + "Wake Word", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + shadows: [ + Helpers.dropShadowRegular, + ], + fontSize: 40), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + ref.watch(voiceAssistantStateProvider.select((value) => value.wakeWord)) ?? "Not Set", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + shadows: [ + Helpers.dropShadowRegular, + ], + fontSize: 40, + ), + ), + const SizedBox( + width: 50, + ), + + ], + ), + ), + ), + const SizedBox( + height: 8, + ) + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart new file mode 100644 index 0000000..ee0365a --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart @@ -0,0 +1,111 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:protos/val_api.dart'; + +class VoiceAssistantSettingsTile extends ConsumerStatefulWidget { + final IconData icon; + final String title; + final bool hasSwich; + final VoidCallback voidCallback; + const VoiceAssistantSettingsTile({ + Key? key, + required this.icon, + required this.title, + required this.hasSwich, + required this.voidCallback, + }) : super(key: key); + + @override + VoiceAssistantSettingsTileState createState() => VoiceAssistantSettingsTileState(); +} + +class VoiceAssistantSettingsTileState extends ConsumerState { + bool isSwitchOn = true; + @override + Widget build(BuildContext context) { + isSwitchOn = ref.watch(voiceAssistantStateProvider.select((voiceAssistant) => voiceAssistant.isVoiceAssistantEnable)); + return Column( + children: [ + GestureDetector( + onTap: isSwitchOn ? widget.voidCallback : () {}, + child: Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: isSwitchOn ? [0.3, 1] : [0.8, 1], + colors: isSwitchOn + ? [Colors.black, Colors.black12] + : [ + const Color.fromARGB(50, 0, 0, 0), + Colors.transparent + ], + ), + ), + child: Card( + color: Colors.transparent, + elevation: 5, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 24), + child: Row( + children: [ + Icon( + widget.icon, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + const SizedBox(width: 24), + Expanded( + child: Text( + widget.title, + style: const TextStyle(fontSize: 40), + ), + ), + widget.hasSwich + ? Container( + width: 126, + height: 80, + decoration: const ShapeDecoration( + color: + AGLDemoColors.gradientBackgroundDarkColor, + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 4, + )), + ), + child: FittedBox( + fit: BoxFit.fill, + child: Switch( + value: isSwitchOn, + onChanged: (bool value) async { + var voiceAgentClient = ref.read(voiceAgentClientProvider); + ServiceStatus status = await voiceAgentClient.checkServiceStatus(); + ref.read(voiceAssistantStateProvider.notifier).toggleVoiceAssistant(status); + setState(() { + isSwitchOn = value; + }); + // This is called when the user toggles the switch. + }, + inactiveTrackColor: Colors.transparent, + activeTrackColor: Colors.transparent, + thumbColor: + MaterialStateProperty.all( + AGLDemoColors.periwinkleColor)), + ), + ) + : const SizedBox(), + ], + ), + ), + ) + ), + ), + const SizedBox( + height: 8, + ) + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart new file mode 100644 index 0000000..d4bdd48 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart @@ -0,0 +1,102 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class VoiceAssistantTile extends ConsumerStatefulWidget { + final IconData icon; + final String title; + final bool hasSwitch; + final VoidCallback voidCallback; + final bool isSwitchOn; + const VoiceAssistantTile({super.key, required this.icon, required this.title, required this.hasSwitch, required this.voidCallback,required this.isSwitchOn}); + + @override + ConsumerState createState() => _VoiceAssistantTileState(); +} + +class _VoiceAssistantTileState extends ConsumerState { + bool isSwitchOn = true; + @override + Widget build(BuildContext context) { + isSwitchOn = widget.isSwitchOn; + return Column( + children: [ + Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: isSwitchOn ? [0.3, 1] : [0.8, 1], + colors: isSwitchOn + ? [Colors.black, Colors.black12] + : [ + const Color.fromARGB(50, 0, 0, 0), + Colors.transparent + ], + ), + ), + child: Card( + + color: Colors.transparent, + elevation: 5, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 24), + child: Row( + children: [ + Icon( + widget.icon, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + const SizedBox(width: 24), + Expanded( + child: Text( + widget.title, + style: const TextStyle(fontSize: 40), + ), + ), + widget.hasSwitch + ? Container( + width: 126, + height: 80, + decoration: const ShapeDecoration( + color: + AGLDemoColors.gradientBackgroundDarkColor, + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 4, + )), + ), + child: FittedBox( + fit: BoxFit.fill, + child: Switch( + value: isSwitchOn, + onChanged: (bool value) { + setState(() { + isSwitchOn = value; + }); + widget.voidCallback(); + }, + inactiveTrackColor: Colors.transparent, + activeTrackColor: Colors.transparent, + thumbColor: + MaterialStateProperty.all( + AGLDemoColors.periwinkleColor)), + ), + ) + : const SizedBox(), + ], + ), + ), + ) + ), + const SizedBox( + height: 14, + ) + ], + ); + } +} + + diff --git a/lib/presentation/screens/settings/widgets/settings_content.dart b/lib/presentation/screens/settings/widgets/settings_content.dart index 6d0df50..458677c 100644 --- a/lib/presentation/screens/settings/widgets/settings_content.dart +++ b/lib/presentation/screens/settings/widgets/settings_content.dart @@ -1,6 +1,7 @@ import 'package:flutter_ics_homescreen/export.dart'; import '../../../custom_icons/custom_icons.dart'; +import '../settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart'; class Settings extends ConsumerWidget { const Settings({ @@ -55,6 +56,14 @@ class Settings extends ConsumerWidget { voidCallback: () { ref.read(appProvider.notifier).update(AppState.audioSettings); }), + VoiceAssistantSettingsTile( + icon: Icons.keyboard_voice_outlined, + title: "Voice Assistant", + hasSwich: true, + voidCallback: (){ + ref.read(appProvider.notifier).update(AppState.voiceAssistant); + } + ), SettingsTile( icon: Icons.person_2_outlined, title: 'Profiles', diff --git a/protos/lib/src/generated/voice_agent/voice_agent.pb.dart b/protos/lib/src/generated/voice_agent/voice_agent.pb.dart new file mode 100644 index 0000000..eb6f360 --- /dev/null +++ b/protos/lib/src/generated/voice_agent/voice_agent.pb.dart @@ -0,0 +1,880 @@ +// +// 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: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, + $core.String? wakeWord, + $core.bool? onlineMode, + }) { + final $result = create(); + if (version != null) { + $result.version = version; + } + if (status != null) { + $result.status = status; + } + if (wakeWord != null) { + $result.wakeWord = wakeWord; + } + if (onlineMode != null) { + $result.onlineMode = onlineMode; + } + 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') + ..aOS(3, _omitFieldNames ? '' : 'wakeWord') + ..aOB(4, _omitFieldNames ? '' : 'onlineMode') + ..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); + + @$pb.TagNumber(3) + $core.String get wakeWord => $_getSZ(2); + @$pb.TagNumber(3) + set wakeWord($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasWakeWord() => $_has(2); + @$pb.TagNumber(3) + void clearWakeWord() => clearField(3); + + @$pb.TagNumber(4) + $core.bool get onlineMode => $_getBF(3); + @$pb.TagNumber(4) + set onlineMode($core.bool v) { $_setBool(3, v); } + @$pb.TagNumber(4) + $core.bool hasOnlineMode() => $_has(3); + @$pb.TagNumber(4) + void clearOnlineMode() => clearField(4); +} + +class VoiceAudio extends $pb.GeneratedMessage { + factory VoiceAudio({ + $core.List<$core.int>? audioChunk, + $core.String? audioFormat, + $core.int? sampleRate, + $core.String? language, + }) { + final $result = create(); + if (audioChunk != null) { + $result.audioChunk = audioChunk; + } + if (audioFormat != null) { + $result.audioFormat = audioFormat; + } + if (sampleRate != null) { + $result.sampleRate = sampleRate; + } + if (language != null) { + $result.language = language; + } + return $result; + } + VoiceAudio._() : super(); + factory VoiceAudio.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory VoiceAudio.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'VoiceAudio', createEmptyInstance: create) + ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'audioChunk', $pb.PbFieldType.OY) + ..aOS(2, _omitFieldNames ? '' : 'audioFormat') + ..a<$core.int>(3, _omitFieldNames ? '' : 'sampleRate', $pb.PbFieldType.O3) + ..aOS(4, _omitFieldNames ? '' : 'language') + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + VoiceAudio clone() => VoiceAudio()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + VoiceAudio copyWith(void Function(VoiceAudio) updates) => super.copyWith((message) => updates(message as VoiceAudio)) as VoiceAudio; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static VoiceAudio create() => VoiceAudio._(); + VoiceAudio createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static VoiceAudio getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static VoiceAudio? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get audioChunk => $_getN(0); + @$pb.TagNumber(1) + set audioChunk($core.List<$core.int> v) { $_setBytes(0, v); } + @$pb.TagNumber(1) + $core.bool hasAudioChunk() => $_has(0); + @$pb.TagNumber(1) + void clearAudioChunk() => clearField(1); + + @$pb.TagNumber(2) + $core.String get audioFormat => $_getSZ(1); + @$pb.TagNumber(2) + set audioFormat($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasAudioFormat() => $_has(1); + @$pb.TagNumber(2) + void clearAudioFormat() => clearField(2); + + @$pb.TagNumber(3) + $core.int get sampleRate => $_getIZ(2); + @$pb.TagNumber(3) + set sampleRate($core.int v) { $_setSignedInt32(2, v); } + @$pb.TagNumber(3) + $core.bool hasSampleRate() => $_has(2); + @$pb.TagNumber(3) + void clearSampleRate() => clearField(3); + + @$pb.TagNumber(4) + $core.String get language => $_getSZ(3); + @$pb.TagNumber(4) + set language($core.String v) { $_setString(3, v); } + @$pb.TagNumber(4) + $core.bool hasLanguage() => $_has(3); + @$pb.TagNumber(4) + void clearLanguage() => clearField(4); +} + +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 S_RecognizeVoiceControl extends $pb.GeneratedMessage { + factory S_RecognizeVoiceControl({ + VoiceAudio? audioStream, + NLUModel? nluModel, + $core.String? streamId, + STTFramework? sttFramework, + }) { + final $result = create(); + if (audioStream != null) { + $result.audioStream = audioStream; + } + if (nluModel != null) { + $result.nluModel = nluModel; + } + if (streamId != null) { + $result.streamId = streamId; + } + if (sttFramework != null) { + $result.sttFramework = sttFramework; + } + return $result; + } + S_RecognizeVoiceControl._() : super(); + factory S_RecognizeVoiceControl.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory S_RecognizeVoiceControl.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'S_RecognizeVoiceControl', createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'audioStream', subBuilder: VoiceAudio.create) + ..e(2, _omitFieldNames ? '' : 'nluModel', $pb.PbFieldType.OE, defaultOrMaker: NLUModel.SNIPS, valueOf: NLUModel.valueOf, enumValues: NLUModel.values) + ..aOS(3, _omitFieldNames ? '' : 'streamId') + ..e(4, _omitFieldNames ? '' : 'sttFramework', $pb.PbFieldType.OE, defaultOrMaker: STTFramework.VOSK, valueOf: STTFramework.valueOf, enumValues: STTFramework.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') + S_RecognizeVoiceControl clone() => S_RecognizeVoiceControl()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + S_RecognizeVoiceControl copyWith(void Function(S_RecognizeVoiceControl) updates) => super.copyWith((message) => updates(message as S_RecognizeVoiceControl)) as S_RecognizeVoiceControl; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static S_RecognizeVoiceControl create() => S_RecognizeVoiceControl._(); + S_RecognizeVoiceControl createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static S_RecognizeVoiceControl getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static S_RecognizeVoiceControl? _defaultInstance; + + @$pb.TagNumber(1) + VoiceAudio get audioStream => $_getN(0); + @$pb.TagNumber(1) + set audioStream(VoiceAudio v) { setField(1, v); } + @$pb.TagNumber(1) + $core.bool hasAudioStream() => $_has(0); + @$pb.TagNumber(1) + void clearAudioStream() => clearField(1); + @$pb.TagNumber(1) + VoiceAudio ensureAudioStream() => $_ensure(0); + + @$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) + $core.String get streamId => $_getSZ(2); + @$pb.TagNumber(3) + set streamId($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasStreamId() => $_has(2); + @$pb.TagNumber(3) + void clearStreamId() => clearField(3); + + @$pb.TagNumber(4) + STTFramework get sttFramework => $_getN(3); + @$pb.TagNumber(4) + set sttFramework(STTFramework v) { setField(4, v); } + @$pb.TagNumber(4) + $core.bool hasSttFramework() => $_has(3); + @$pb.TagNumber(4) + void clearSttFramework() => clearField(4); +} + +class RecognizeVoiceControl extends $pb.GeneratedMessage { + factory RecognizeVoiceControl({ + RecordAction? action, + NLUModel? nluModel, + RecordMode? recordMode, + $core.String? streamId, + STTFramework? sttFramework, + OnlineMode? onlineMode, + }) { + 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; + } + if (sttFramework != null) { + $result.sttFramework = sttFramework; + } + if (onlineMode != null) { + $result.onlineMode = onlineMode; + } + return $result; + } + RecognizeVoiceControl._() : super(); + factory RecognizeVoiceControl.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory RecognizeVoiceControl.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RecognizeVoiceControl', 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') + ..e(5, _omitFieldNames ? '' : 'sttFramework', $pb.PbFieldType.OE, defaultOrMaker: STTFramework.VOSK, valueOf: STTFramework.valueOf, enumValues: STTFramework.values) + ..e(6, _omitFieldNames ? '' : 'onlineMode', $pb.PbFieldType.OE, defaultOrMaker: OnlineMode.ONLINE, valueOf: OnlineMode.valueOf, enumValues: OnlineMode.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') + RecognizeVoiceControl clone() => RecognizeVoiceControl()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + RecognizeVoiceControl copyWith(void Function(RecognizeVoiceControl) updates) => super.copyWith((message) => updates(message as RecognizeVoiceControl)) as RecognizeVoiceControl; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RecognizeVoiceControl create() => RecognizeVoiceControl._(); + RecognizeVoiceControl createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static RecognizeVoiceControl getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static RecognizeVoiceControl? _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); + + @$pb.TagNumber(5) + STTFramework get sttFramework => $_getN(4); + @$pb.TagNumber(5) + set sttFramework(STTFramework v) { setField(5, v); } + @$pb.TagNumber(5) + $core.bool hasSttFramework() => $_has(4); + @$pb.TagNumber(5) + void clearSttFramework() => clearField(5); + + @$pb.TagNumber(6) + OnlineMode get onlineMode => $_getN(5); + @$pb.TagNumber(6) + set onlineMode(OnlineMode v) { setField(6, v); } + @$pb.TagNumber(6) + $core.bool hasOnlineMode() => $_has(5); + @$pb.TagNumber(6) + void clearOnlineMode() => clearField(6); +} + +class RecognizeTextControl extends $pb.GeneratedMessage { + factory RecognizeTextControl({ + $core.String? textCommand, + NLUModel? nluModel, + }) { + final $result = create(); + if (textCommand != null) { + $result.textCommand = textCommand; + } + if (nluModel != null) { + $result.nluModel = nluModel; + } + return $result; + } + RecognizeTextControl._() : super(); + factory RecognizeTextControl.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory RecognizeTextControl.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RecognizeTextControl', createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'textCommand') + ..e(2, _omitFieldNames ? '' : 'nluModel', $pb.PbFieldType.OE, defaultOrMaker: NLUModel.SNIPS, valueOf: NLUModel.valueOf, enumValues: NLUModel.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') + RecognizeTextControl clone() => RecognizeTextControl()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + RecognizeTextControl copyWith(void Function(RecognizeTextControl) updates) => super.copyWith((message) => updates(message as RecognizeTextControl)) as RecognizeTextControl; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RecognizeTextControl create() => RecognizeTextControl._(); + RecognizeTextControl createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static RecognizeTextControl getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static RecognizeTextControl? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get textCommand => $_getSZ(0); + @$pb.TagNumber(1) + set textCommand($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasTextCommand() => $_has(0); + @$pb.TagNumber(1) + void clearTextCommand() => 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); +} + +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); +} + +class VoiceAgentServiceApi { + $pb.RpcClient _client; + VoiceAgentServiceApi(this._client); + + $async.Future checkServiceStatus($pb.ClientContext? ctx, Empty request) => + _client.invoke(ctx, 'VoiceAgentService', 'CheckServiceStatus', request, ServiceStatus()) + ; + $async.Future s_DetectWakeWord($pb.ClientContext? ctx, VoiceAudio request) => + _client.invoke(ctx, 'VoiceAgentService', 'S_DetectWakeWord', request, WakeWordStatus()) + ; + $async.Future detectWakeWord($pb.ClientContext? ctx, Empty request) => + _client.invoke(ctx, 'VoiceAgentService', 'DetectWakeWord', request, WakeWordStatus()) + ; + $async.Future s_RecognizeVoiceCommand($pb.ClientContext? ctx, S_RecognizeVoiceControl request) => + _client.invoke(ctx, 'VoiceAgentService', 'S_RecognizeVoiceCommand', request, RecognizeResult()) + ; + $async.Future recognizeVoiceCommand($pb.ClientContext? ctx, RecognizeVoiceControl request) => + _client.invoke(ctx, 'VoiceAgentService', 'RecognizeVoiceCommand', request, RecognizeResult()) + ; + $async.Future recognizeTextCommand($pb.ClientContext? ctx, RecognizeTextControl request) => + _client.invoke(ctx, 'VoiceAgentService', 'RecognizeTextCommand', request, RecognizeResult()) + ; + $async.Future executeCommand($pb.ClientContext? ctx, ExecuteInput request) => + _client.invoke(ctx, 'VoiceAgentService', 'ExecuteCommand', request, ExecuteResult()) + ; +} + + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/protos/lib/src/generated/voice_agent/voice_agent.pbenum.dart b/protos/lib/src/generated/voice_agent/voice_agent.pbenum.dart new file mode 100644 index 0000000..51e7427 --- /dev/null +++ b/protos/lib/src/generated/voice_agent/voice_agent.pbenum.dart @@ -0,0 +1,138 @@ +// +// 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 STTFramework extends $pb.ProtobufEnum { + static const STTFramework VOSK = STTFramework._(0, _omitEnumNames ? '' : 'VOSK'); + static const STTFramework WHISPER = STTFramework._(1, _omitEnumNames ? '' : 'WHISPER'); + + static const $core.List values = [ + VOSK, + WHISPER, + ]; + + static final $core.Map<$core.int, STTFramework> _byValue = $pb.ProtobufEnum.initByValue(values); + static STTFramework? valueOf($core.int value) => _byValue[value]; + + const STTFramework._($core.int v, $core.String n) : super(v, n); +} + +class OnlineMode extends $pb.ProtobufEnum { + static const OnlineMode ONLINE = OnlineMode._(0, _omitEnumNames ? '' : 'ONLINE'); + static const OnlineMode OFFLINE = OnlineMode._(1, _omitEnumNames ? '' : 'OFFLINE'); + + static const $core.List values = [ + ONLINE, + OFFLINE, + ]; + + static final $core.Map<$core.int, OnlineMode> _byValue = $pb.ProtobufEnum.initByValue(values); + static OnlineMode? valueOf($core.int value) => _byValue[value]; + + const OnlineMode._($core.int v, $core.String n) : super(v, n); +} + +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 RecognizeStatusType TEXT_NOT_RECOGNIZED = RecognizeStatusType._(5, _omitEnumNames ? '' : 'TEXT_NOT_RECOGNIZED'); + static const RecognizeStatusType NLU_MODEL_NOT_SUPPORTED = RecognizeStatusType._(6, _omitEnumNames ? '' : 'NLU_MODEL_NOT_SUPPORTED'); + + static const $core.List values = [ + REC_ERROR, + REC_SUCCESS, + REC_PROCESSING, + VOICE_NOT_RECOGNIZED, + INTENT_NOT_RECOGNIZED, + TEXT_NOT_RECOGNIZED, + NLU_MODEL_NOT_SUPPORTED, + ]; + + 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/protos/lib/src/generated/voice_agent/voice_agent.pbgrpc.dart b/protos/lib/src/generated/voice_agent/voice_agent.pbgrpc.dart new file mode 100644 index 0000000..e972432 --- /dev/null +++ b/protos/lib/src/generated/voice_agent/voice_agent.pbgrpc.dart @@ -0,0 +1,167 @@ +// +// 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 _$s_DetectWakeWord = $grpc.ClientMethod<$0.VoiceAudio, $0.WakeWordStatus>( + '/VoiceAgentService/S_DetectWakeWord', + ($0.VoiceAudio value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.WakeWordStatus.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 _$s_RecognizeVoiceCommand = $grpc.ClientMethod<$0.S_RecognizeVoiceControl, $0.RecognizeResult>( + '/VoiceAgentService/S_RecognizeVoiceCommand', + ($0.S_RecognizeVoiceControl value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.RecognizeResult.fromBuffer(value)); + static final _$recognizeVoiceCommand = $grpc.ClientMethod<$0.RecognizeVoiceControl, $0.RecognizeResult>( + '/VoiceAgentService/RecognizeVoiceCommand', + ($0.RecognizeVoiceControl value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.RecognizeResult.fromBuffer(value)); + static final _$recognizeTextCommand = $grpc.ClientMethod<$0.RecognizeTextControl, $0.RecognizeResult>( + '/VoiceAgentService/RecognizeTextCommand', + ($0.RecognizeTextControl value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.RecognizeResult.fromBuffer(value)); + static final _$executeCommand = $grpc.ClientMethod<$0.ExecuteInput, $0.ExecuteResult>( + '/VoiceAgentService/ExecuteCommand', + ($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> s_DetectWakeWord($async.Stream<$0.VoiceAudio> request, {$grpc.CallOptions? options}) { + return $createStreamingCall(_$s_DetectWakeWord, 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> s_RecognizeVoiceCommand($async.Stream<$0.S_RecognizeVoiceControl> request, {$grpc.CallOptions? options}) { + return $createStreamingCall(_$s_RecognizeVoiceCommand, request, options: options).single; + } + + $grpc.ResponseFuture<$0.RecognizeResult> recognizeVoiceCommand($async.Stream<$0.RecognizeVoiceControl> request, {$grpc.CallOptions? options}) { + return $createStreamingCall(_$recognizeVoiceCommand, request, options: options).single; + } + + $grpc.ResponseFuture<$0.RecognizeResult> recognizeTextCommand($0.RecognizeTextControl request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$recognizeTextCommand, request, options: options); + } + + $grpc.ResponseFuture<$0.ExecuteResult> executeCommand($0.ExecuteInput request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$executeCommand, 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.VoiceAudio, $0.WakeWordStatus>( + 'S_DetectWakeWord', + s_DetectWakeWord, + true, + true, + ($core.List<$core.int> value) => $0.VoiceAudio.fromBuffer(value), + ($0.WakeWordStatus 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.S_RecognizeVoiceControl, $0.RecognizeResult>( + 'S_RecognizeVoiceCommand', + s_RecognizeVoiceCommand, + true, + false, + ($core.List<$core.int> value) => $0.S_RecognizeVoiceControl.fromBuffer(value), + ($0.RecognizeResult value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.RecognizeVoiceControl, $0.RecognizeResult>( + 'RecognizeVoiceCommand', + recognizeVoiceCommand, + true, + false, + ($core.List<$core.int> value) => $0.RecognizeVoiceControl.fromBuffer(value), + ($0.RecognizeResult value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.RecognizeTextControl, $0.RecognizeResult>( + 'RecognizeTextCommand', + recognizeTextCommand_Pre, + false, + false, + ($core.List<$core.int> value) => $0.RecognizeTextControl.fromBuffer(value), + ($0.RecognizeResult value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.ExecuteInput, $0.ExecuteResult>( + 'ExecuteCommand', + executeCommand_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.RecognizeResult> recognizeTextCommand_Pre($grpc.ServiceCall call, $async.Future<$0.RecognizeTextControl> request) async { + return recognizeTextCommand(call, await request); + } + + $async.Future<$0.ExecuteResult> executeCommand_Pre($grpc.ServiceCall call, $async.Future<$0.ExecuteInput> request) async { + return executeCommand(call, await request); + } + + $async.Future<$0.ServiceStatus> checkServiceStatus($grpc.ServiceCall call, $0.Empty request); + $async.Stream<$0.WakeWordStatus> s_DetectWakeWord($grpc.ServiceCall call, $async.Stream<$0.VoiceAudio> request); + $async.Stream<$0.WakeWordStatus> detectWakeWord($grpc.ServiceCall call, $0.Empty request); + $async.Future<$0.RecognizeResult> s_RecognizeVoiceCommand($grpc.ServiceCall call, $async.Stream<$0.S_RecognizeVoiceControl> request); + $async.Future<$0.RecognizeResult> recognizeVoiceCommand($grpc.ServiceCall call, $async.Stream<$0.RecognizeVoiceControl> request); + $async.Future<$0.RecognizeResult> recognizeTextCommand($grpc.ServiceCall call, $0.RecognizeTextControl request); + $async.Future<$0.ExecuteResult> executeCommand($grpc.ServiceCall call, $0.ExecuteInput request); +} diff --git a/protos/lib/src/generated/voice_agent/voice_agent.pbjson.dart b/protos/lib/src/generated/voice_agent/voice_agent.pbjson.dart new file mode 100644 index 0000000..2a824c1 --- /dev/null +++ b/protos/lib/src/generated/voice_agent/voice_agent.pbjson.dart @@ -0,0 +1,326 @@ +// +// 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 sTTFrameworkDescriptor instead') +const STTFramework$json = { + '1': 'STTFramework', + '2': [ + {'1': 'VOSK', '2': 0}, + {'1': 'WHISPER', '2': 1}, + ], +}; + +/// Descriptor for `STTFramework`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List sTTFrameworkDescriptor = $convert.base64Decode( + 'CgxTVFRGcmFtZXdvcmsSCAoEVk9TSxAAEgsKB1dISVNQRVIQAQ=='); + +@$core.Deprecated('Use onlineModeDescriptor instead') +const OnlineMode$json = { + '1': 'OnlineMode', + '2': [ + {'1': 'ONLINE', '2': 0}, + {'1': 'OFFLINE', '2': 1}, + ], +}; + +/// Descriptor for `OnlineMode`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List onlineModeDescriptor = $convert.base64Decode( + 'CgpPbmxpbmVNb2RlEgoKBk9OTElORRAAEgsKB09GRkxJTkUQAQ=='); + +@$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}, + {'1': 'TEXT_NOT_RECOGNIZED', '2': 5}, + {'1': 'NLU_MODEL_NOT_SUPPORTED', '2': 6}, + ], +}; + +/// Descriptor for `RecognizeStatusType`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List recognizeStatusTypeDescriptor = $convert.base64Decode( + 'ChNSZWNvZ25pemVTdGF0dXNUeXBlEg0KCVJFQ19FUlJPUhAAEg8KC1JFQ19TVUNDRVNTEAESEg' + 'oOUkVDX1BST0NFU1NJTkcQAhIYChRWT0lDRV9OT1RfUkVDT0dOSVpFRBADEhkKFUlOVEVOVF9O' + 'T1RfUkVDT0dOSVpFRBAEEhcKE1RFWFRfTk9UX1JFQ09HTklaRUQQBRIbChdOTFVfTU9ERUxfTk' + '9UX1NVUFBPUlRFRBAG'); + +@$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'}, + {'1': 'wake_word', '3': 3, '4': 1, '5': 9, '10': 'wakeWord'}, + {'1': 'online_mode', '3': 4, '4': 1, '5': 8, '10': 'onlineMode'}, + ], +}; + +/// Descriptor for `ServiceStatus`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List serviceStatusDescriptor = $convert.base64Decode( + 'Cg1TZXJ2aWNlU3RhdHVzEhgKB3ZlcnNpb24YASABKAlSB3ZlcnNpb24SFgoGc3RhdHVzGAIgAS' + 'gIUgZzdGF0dXMSGwoJd2FrZV93b3JkGAMgASgJUgh3YWtlV29yZBIfCgtvbmxpbmVfbW9kZRgE' + 'IAEoCFIKb25saW5lTW9kZQ=='); + +@$core.Deprecated('Use voiceAudioDescriptor instead') +const VoiceAudio$json = { + '1': 'VoiceAudio', + '2': [ + {'1': 'audio_chunk', '3': 1, '4': 1, '5': 12, '10': 'audioChunk'}, + {'1': 'audio_format', '3': 2, '4': 1, '5': 9, '10': 'audioFormat'}, + {'1': 'sample_rate', '3': 3, '4': 1, '5': 5, '10': 'sampleRate'}, + {'1': 'language', '3': 4, '4': 1, '5': 9, '10': 'language'}, + ], +}; + +/// Descriptor for `VoiceAudio`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List voiceAudioDescriptor = $convert.base64Decode( + 'CgpWb2ljZUF1ZGlvEh8KC2F1ZGlvX2NodW5rGAEgASgMUgphdWRpb0NodW5rEiEKDGF1ZGlvX2' + 'Zvcm1hdBgCIAEoCVILYXVkaW9Gb3JtYXQSHwoLc2FtcGxlX3JhdGUYAyABKAVSCnNhbXBsZVJh' + 'dGUSGgoIbGFuZ3VhZ2UYBCABKAlSCGxhbmd1YWdl'); + +@$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 s_RecognizeVoiceControlDescriptor instead') +const S_RecognizeVoiceControl$json = { + '1': 'S_RecognizeVoiceControl', + '2': [ + {'1': 'audio_stream', '3': 1, '4': 1, '5': 11, '6': '.VoiceAudio', '10': 'audioStream'}, + {'1': 'nlu_model', '3': 2, '4': 1, '5': 14, '6': '.NLUModel', '10': 'nluModel'}, + {'1': 'stream_id', '3': 3, '4': 1, '5': 9, '10': 'streamId'}, + {'1': 'stt_framework', '3': 4, '4': 1, '5': 14, '6': '.STTFramework', '10': 'sttFramework'}, + ], +}; + +/// Descriptor for `S_RecognizeVoiceControl`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List s_RecognizeVoiceControlDescriptor = $convert.base64Decode( + 'ChdTX1JlY29nbml6ZVZvaWNlQ29udHJvbBIuCgxhdWRpb19zdHJlYW0YASABKAsyCy5Wb2ljZU' + 'F1ZGlvUgthdWRpb1N0cmVhbRImCglubHVfbW9kZWwYAiABKA4yCS5OTFVNb2RlbFIIbmx1TW9k' + 'ZWwSGwoJc3RyZWFtX2lkGAMgASgJUghzdHJlYW1JZBIyCg1zdHRfZnJhbWV3b3JrGAQgASgOMg' + '0uU1RURnJhbWV3b3JrUgxzdHRGcmFtZXdvcms='); + +@$core.Deprecated('Use recognizeVoiceControlDescriptor instead') +const RecognizeVoiceControl$json = { + '1': 'RecognizeVoiceControl', + '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'}, + {'1': 'stt_framework', '3': 5, '4': 1, '5': 14, '6': '.STTFramework', '10': 'sttFramework'}, + {'1': 'online_mode', '3': 6, '4': 1, '5': 14, '6': '.OnlineMode', '10': 'onlineMode'}, + ], +}; + +/// Descriptor for `RecognizeVoiceControl`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List recognizeVoiceControlDescriptor = $convert.base64Decode( + 'ChVSZWNvZ25pemVWb2ljZUNvbnRyb2wSJQoGYWN0aW9uGAEgASgOMg0uUmVjb3JkQWN0aW9uUg' + 'ZhY3Rpb24SJgoJbmx1X21vZGVsGAIgASgOMgkuTkxVTW9kZWxSCG5sdU1vZGVsEiwKC3JlY29y' + 'ZF9tb2RlGAMgASgOMgsuUmVjb3JkTW9kZVIKcmVjb3JkTW9kZRIbCglzdHJlYW1faWQYBCABKA' + 'lSCHN0cmVhbUlkEjIKDXN0dF9mcmFtZXdvcmsYBSABKA4yDS5TVFRGcmFtZXdvcmtSDHN0dEZy' + 'YW1ld29yaxIsCgtvbmxpbmVfbW9kZRgGIAEoDjILLk9ubGluZU1vZGVSCm9ubGluZU1vZGU='); + +@$core.Deprecated('Use recognizeTextControlDescriptor instead') +const RecognizeTextControl$json = { + '1': 'RecognizeTextControl', + '2': [ + {'1': 'text_command', '3': 1, '4': 1, '5': 9, '10': 'textCommand'}, + {'1': 'nlu_model', '3': 2, '4': 1, '5': 14, '6': '.NLUModel', '10': 'nluModel'}, + ], +}; + +/// Descriptor for `RecognizeTextControl`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List recognizeTextControlDescriptor = $convert.base64Decode( + 'ChRSZWNvZ25pemVUZXh0Q29udHJvbBIhCgx0ZXh0X2NvbW1hbmQYASABKAlSC3RleHRDb21tYW' + '5kEiYKCW5sdV9tb2RlbBgCIAEoDjIJLk5MVU1vZGVsUghubHVNb2RlbA=='); + +@$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'); + +const $core.Map<$core.String, $core.dynamic> VoiceAgentServiceBase$json = { + '1': 'VoiceAgentService', + '2': [ + {'1': 'CheckServiceStatus', '2': '.Empty', '3': '.ServiceStatus'}, + {'1': 'S_DetectWakeWord', '2': '.VoiceAudio', '3': '.WakeWordStatus', '5': true, '6': true}, + {'1': 'DetectWakeWord', '2': '.Empty', '3': '.WakeWordStatus', '6': true}, + {'1': 'S_RecognizeVoiceCommand', '2': '.S_RecognizeVoiceControl', '3': '.RecognizeResult', '5': true}, + {'1': 'RecognizeVoiceCommand', '2': '.RecognizeVoiceControl', '3': '.RecognizeResult', '5': true}, + {'1': 'RecognizeTextCommand', '2': '.RecognizeTextControl', '3': '.RecognizeResult'}, + {'1': 'ExecuteCommand', '2': '.ExecuteInput', '3': '.ExecuteResult'}, + ], +}; + +@$core.Deprecated('Use voiceAgentServiceDescriptor instead') +const $core.Map<$core.String, $core.Map<$core.String, $core.dynamic>> VoiceAgentServiceBase$messageJson = { + '.Empty': Empty$json, + '.ServiceStatus': ServiceStatus$json, + '.VoiceAudio': VoiceAudio$json, + '.WakeWordStatus': WakeWordStatus$json, + '.S_RecognizeVoiceControl': S_RecognizeVoiceControl$json, + '.RecognizeResult': RecognizeResult$json, + '.IntentSlot': IntentSlot$json, + '.RecognizeVoiceControl': RecognizeVoiceControl$json, + '.RecognizeTextControl': RecognizeTextControl$json, + '.ExecuteInput': ExecuteInput$json, + '.ExecuteResult': ExecuteResult$json, +}; + +/// Descriptor for `VoiceAgentService`. Decode as a `google.protobuf.ServiceDescriptorProto`. +final $typed_data.Uint8List voiceAgentServiceDescriptor = $convert.base64Decode( + 'ChFWb2ljZUFnZW50U2VydmljZRIsChJDaGVja1NlcnZpY2VTdGF0dXMSBi5FbXB0eRoOLlNlcn' + 'ZpY2VTdGF0dXMSNAoQU19EZXRlY3RXYWtlV29yZBILLlZvaWNlQXVkaW8aDy5XYWtlV29yZFN0' + 'YXR1cygBMAESKwoORGV0ZWN0V2FrZVdvcmQSBi5FbXB0eRoPLldha2VXb3JkU3RhdHVzMAESRw' + 'oXU19SZWNvZ25pemVWb2ljZUNvbW1hbmQSGC5TX1JlY29nbml6ZVZvaWNlQ29udHJvbBoQLlJl' + 'Y29nbml6ZVJlc3VsdCgBEkMKFVJlY29nbml6ZVZvaWNlQ29tbWFuZBIWLlJlY29nbml6ZVZvaW' + 'NlQ29udHJvbBoQLlJlY29nbml6ZVJlc3VsdCgBEj8KFFJlY29nbml6ZVRleHRDb21tYW5kEhUu' + 'UmVjb2duaXplVGV4dENvbnRyb2waEC5SZWNvZ25pemVSZXN1bHQSLwoORXhlY3V0ZUNvbW1hbm' + 'QSDS5FeGVjdXRlSW5wdXQaDi5FeGVjdXRlUmVzdWx0'); + diff --git a/protos/lib/val_api.dart b/protos/lib/val_api.dart index de6dfac..713a393 100644 --- a/protos/lib/val_api.dart +++ b/protos/lib/val_api.dart @@ -13,4 +13,9 @@ export 'src/generated/kuksa/val/v1/val.pbenum.dart'; export 'src/generated/kuksa/val/v1/val.pbjson.dart'; export 'src/generated/kuksa/val/v1/val.pbgrpc.dart'; +export 'src/generated/voice_agent/voice_agent.pb.dart'; +export 'src/generated/voice_agent/voice_agent.pbenum.dart'; +export 'src/generated/voice_agent/voice_agent.pbjson.dart'; +export 'src/generated/voice_agent/voice_agent.pbgrpc.dart'; + export 'package:grpc/grpc.dart'; diff --git a/protos/protos/voice_agent/voice_agent.proto b/protos/protos/voice_agent/voice_agent.proto new file mode 100644 index 0000000..f1164ff --- /dev/null +++ b/protos/protos/voice_agent/voice_agent.proto @@ -0,0 +1,120 @@ +syntax = "proto3"; + + +service VoiceAgentService { + rpc CheckServiceStatus(Empty) returns (ServiceStatus); + rpc S_DetectWakeWord(stream VoiceAudio) returns (stream WakeWordStatus); // Stream version of DetectWakeWord, assumes audio is coming from client + rpc DetectWakeWord(Empty) returns (stream WakeWordStatus); + rpc S_RecognizeVoiceCommand(stream S_RecognizeVoiceControl) returns (RecognizeResult); // Stream version of RecognizeVoiceCommand, assumes audio is coming from client + rpc RecognizeVoiceCommand(stream RecognizeVoiceControl) returns (RecognizeResult); + rpc RecognizeTextCommand(RecognizeTextControl) returns (RecognizeResult); + rpc ExecuteCommand(ExecuteInput) returns (ExecuteResult); +} + +enum STTFramework { + VOSK = 0; + WHISPER = 1; +} + +enum OnlineMode { + ONLINE = 0; + OFFLINE = 1; +} + +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; + TEXT_NOT_RECOGNIZED = 5; + NLU_MODEL_NOT_SUPPORTED = 6; +} + +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; + string wake_word = 3; + bool online_mode = 4; +} + +message VoiceAudio { + bytes audio_chunk = 1; + string audio_format = 2; + int32 sample_rate = 3; + string language = 4; +} + +message WakeWordStatus { + bool status = 1; +} + +message S_RecognizeVoiceControl { + VoiceAudio audio_stream = 1; + NLUModel nlu_model = 2; + string stream_id = 3; + STTFramework stt_framework = 4; +} + +message RecognizeVoiceControl { + RecordAction action = 1; + NLUModel nlu_model = 2; + RecordMode record_mode = 3; + string stream_id = 4; + STTFramework stt_framework = 5; + OnlineMode online_mode = 6; +} + +message RecognizeTextControl { + string text_command = 1; + NLUModel nlu_model = 2; +} + +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; +} -- 2.16.6