From: Anuj Solanki Date: Sun, 29 Sep 2024 16:01:03 +0000 (+0530) Subject: Integrate voice assistant into flutter-ics-homescreen X-Git-Tag: 19.90.0~10 X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=commitdiff_plain;h=f870bbe3c49d421ff8ea561752b3b0a38ad04e96;p=apps%2Fflutter-ics-homescreen.git Integrate voice assistant into flutter-ics-homescreen - 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 --- 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 0000000..059864a Binary files /dev/null and b/assets/VoiceAssistantBottomSheetBg.png differ 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; +}