diff options
author | Anuj-S62 <anuj603362@gmail.com> | 2024-07-11 15:18:31 +0530 |
---|---|---|
committer | Anuj-S62 <anuj603362@gmail.com> | 2024-07-21 19:52:11 +0530 |
commit | 0b59086cdddd40689e57969aa7914ba38f1ec2dd (patch) | |
tree | 975c38663f57d497eeab0e2649e51308aef9a755 /lib/screens | |
parent | ecd34435c1a74b39bf41d59ad479fdc85d0afb7b (diff) |
Update Voice Agent Flutter App
- update voice-agent flutter app to use whisper AI for
speech-to-text functionality.
- Integrated SharedPreferences to store the application state.
Bug-AGL: SPEC-5200
Change-Id: I9a05b1d135c1fa07949333391ff828f166b7fe8e
Signed-off-by: Anuj-S62 <anuj603362@gmail.com>
Diffstat (limited to 'lib/screens')
-rw-r--r-- | lib/screens/home_screen.dart | 380 | ||||
-rw-r--r-- | lib/screens/loading_screen.dart | 81 |
2 files changed, 363 insertions, 98 deletions
diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index a9d567b..4c2407f 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,8 +1,10 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter_voiceassistant/widgets/online_mode_choice.dart'; import 'package:flutter_voiceassistant/widgets/try_commands.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'dart:async'; import '../models/app_state.dart'; import '../widgets/nlu_engine_choice.dart'; @@ -13,6 +15,7 @@ import '../widgets/chat_section.dart'; import '../grpc/generated/voice_agent.pbgrpc.dart'; import '../grpc/voice_agent_client.dart'; import '../utils/app_config.dart'; +import '../widgets/stt_model_choice.dart'; class HomePage extends StatefulWidget { final AppConfig config; @@ -36,18 +39,30 @@ class HomePageState extends State<HomePage> { super.initState(); _config = widget.config; // Initialize _config in the initState _wakeWord = widget.wakeWord; // Initialize _wakeWord in the initState - addChatMessage( - "Assistant in Manual mode. You can send commands directly by pressing the record button."); + final appState = context.read<AppState>(); + if(appState.isWakeWordMode){ + addChatMessage( + 'Switched to Wake Word mode. I\'ll listen for the wake word "$_wakeWord" before responding.'); + _startWakeWordDetection(context); + } + else{ + addChatMessage( + "Assistant in Manual mode. You can send commands directly by pressing the record button."); + } + } - void changeAssistantMode(BuildContext context, AssistantMode newMode) { + Future<void> changeAssistantMode(BuildContext context, AssistantMode newMode) async { final appState = context.read<AppState>(); clearChatMessages(); appState.streamId = ""; appState.isWakeWordDetected = false; appState.isCommandProcessing = false; + SharedPreferences prefs = await SharedPreferences.getInstance(); + if (newMode == AssistantMode.wakeWord) { + await prefs.setBool('isWakeWordMode', true); addChatMessage( 'Switched to Wake Word mode. I\'ll listen for the wake word "$_wakeWord" before responding.'); @@ -62,6 +77,7 @@ class HomePageState extends State<HomePage> { toggleWakeWordDetection(context, true); } } else if (newMode == AssistantMode.manual) { + prefs.setBool('isWakeWordMode', false); addChatMessage( 'Switched to Manual mode. You can send commands directly by pressing record button.'); @@ -74,15 +90,17 @@ class HomePageState extends State<HomePage> { setState(() {}); // Trigger a rebuild } - void changeIntentEngine(BuildContext context, NLUEngine newEngine) { + Future<void> changeIntentEngine(BuildContext context, NLUEngine newEngine) async { final appState = context.read<AppState>(); - + SharedPreferences prefs = await SharedPreferences.getInstance(); if (newEngine == NLUEngine.snips) { appState.intentEngine = "snips"; + await prefs.setString("intentEngine","snips"); addChatMessage( 'Switched to 🚀 Snips engine. Lets be precise and accurate.'); } else if (newEngine == NLUEngine.rasa) { appState.intentEngine = "rasa"; + await prefs.setString("intentEngine","rasa"); addChatMessage( 'Switched to 🤖 RASA engine. Conversations just got smarter!'); } @@ -90,6 +108,42 @@ class HomePageState extends State<HomePage> { setState(() {}); // Trigger a rebuild } + Future<void> changeSTTFramework(BuildContext context, STTModel newModel) async { + final appState = context.read<AppState>(); + SharedPreferences prefs = await SharedPreferences.getInstance(); + if (newModel == STTModel.vosk) { + appState.sttFramework = "vosk"; + await prefs.setString("sttFramework", "vosk"); + // vosk is fast and efficient + addChatMessage( + 'Switched to 🚀 Vosk framework. Lets be quick and efficient.'); + } else if (newModel == STTModel.whisper) { + appState.sttFramework = "whisper"; + await prefs.setString("sttFramework", "whisper"); + addChatMessage( + 'Switched to 🤖 Whisper framework. Conversations just got smarter!'); + } + print(appState.sttFramework); + setState(() {}); // Trigger a rebuild + } + + Future<void> toggleOnlineMode(BuildContext context, OnlineModeEnum mode) async { + final appState = context.read<AppState>(); + SharedPreferences prefs = await SharedPreferences.getInstance(); + if (mode == OnlineModeEnum.enabled) { + appState.onlineMode = true; + await prefs.setBool('onlineMode', true); + addChatMessage( + 'Switched to Online mode. I\'ll be connected to the internet for better results.'); + } else { + appState.onlineMode = false; + await prefs.setBool('onlineMode', false); + addChatMessage( + 'Switched to Offline mode. I\'ll be disconnected from the internet.'); + } + setState(() {}); // Trigger a rebuild + } + void addChatMessage(String text, {bool isUserMessage = false}) { final newMessage = ChatMessage(text: text, isUserMessage: isUserMessage); setState(() { @@ -123,7 +177,7 @@ class HomePageState extends State<HomePage> { setState( () {}); // Trigger a rebuild to ensure the loading indicator is shown, tis a bad practice though but deosn't heavily affect the performance final response = - await stopRecording(appState.streamId, appState.intentEngine); + await stopRecording(appState.streamId, appState.intentEngine,appState.sttFramework,appState.onlineMode); // Process and store the result if (response.status == RecognizeStatusType.REC_SUCCESS) { appState.commandProcessingText = "Executing command..."; @@ -204,19 +258,31 @@ class HomePageState extends State<HomePage> { } Future<RecognizeResult> stopRecording( - String streamId, String nluModel) async { + 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; + ..recordMode = RecordMode.MANUAL + ..sttFramework = sttFramework + ..onlineMode = onlineMode; + // Create a Stream with the control message final controlStream = Stream.fromIterable([controlMessage]); @@ -375,116 +441,234 @@ class HomePageState extends State<HomePage> { style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold), ), SizedBox(height: 15), - Row( - mainAxisAlignment: MainAxisAlignment.center, + Column( children: [ - Flexible( - flex: 1, - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: Card( - color: _config.theme == "textured-dark" || - _config.theme == "textured-light" - ? Colors.transparent - : null, - elevation: 4, // Add elevation for shadow - shadowColor: _config.theme == "textured-dark" || - _config.theme == "textured-light" - ? Colors.transparent - : null, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Assistant Mode', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + flex: 1, + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: Card( + color: _config.theme == "textured-dark" || + _config.theme == "textured-light" + ? Colors.transparent + : null, + elevation: 4, // Add elevation for shadow + shadowColor: _config.theme == "textured-dark" || + _config.theme == "textured-light" + ? Colors.transparent + : null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Assistant Mode', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 16), // Add spacing if needed + Center( + child: Consumer<AppState>( + builder: (context, appState, _) { + return AssistantModeChoice( + onModeChanged: (newMode) { + changeAssistantMode( + context, newMode); + print(newMode); + }, + theme: _config.theme, + ); + }, + ), + ), + ], ), - SizedBox(height: 16), // Add spacing if needed - Center( - child: Consumer<AppState>( - builder: (context, appState, _) { - return AssistantModeChoice( - onModeChanged: (newMode) { - changeAssistantMode( - context, newMode); - print(newMode); + ), + ), + ), + ), + ), + + SizedBox(width: 20), // Add spacing between buttons + + Flexible( + flex: 1, + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: Card( + color: _config.theme == "textured-dark" || + _config.theme == "textured-light" + ? Colors.transparent + : null, + elevation: 4, // Add elevation for shadow + shadowColor: _config.theme == "textured-dark" || + _config.theme == "textured-light" + ? Colors.transparent + : null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Intent Engine', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 16), // Add spacing if needed + Center( + child: Consumer<AppState>( + builder: (context, appState, _) { + return NLUEngineChoice( + onEngineChanged: (newEngine) { + changeIntentEngine( + context, newEngine); + print(newEngine); + }, + theme: _config.theme, + ); }, - theme: _config.theme, - ); - }, - ), + ), + ), + ], ), - ], + ), ), ), ), ), - ), + ], ), - SizedBox(width: 20), // Add spacing between buttons - - Flexible( - flex: 1, - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: Card( - color: _config.theme == "textured-dark" || + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + flex: 1, + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: Card( + color: _config.theme == "textured-dark" || _config.theme == "textured-light" - ? Colors.transparent - : null, - elevation: 4, // Add elevation for shadow - shadowColor: _config.theme == "textured-dark" || + ? Colors.transparent + : null, + elevation: 4, // Add elevation for shadow + shadowColor: _config.theme == "textured-dark" || _config.theme == "textured-light" - ? Colors.transparent - : null, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Intent Engine', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + ? Colors.transparent + : null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Speech-to-Text Model', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 16), // Add spacing if needed + Center( + child: Consumer<AppState>( + builder: (context, appState, _) { + return STTModelChoice( + onModelChanged: (newModel) { + changeSTTFramework( + context, newModel); + print(newModel); + }, + theme: _config.theme, + ); + }, + ), + ), + ], ), - SizedBox(height: 16), // Add spacing if needed - Center( - child: Consumer<AppState>( - builder: (context, appState, _) { - return NLUEngineChoice( - onEngineChanged: (newEngine) { - changeIntentEngine( - context, newEngine); - print(newEngine); + ), + ), + ), + ), + ), + + SizedBox(width: 20), // Add spacing between buttons + + Flexible( + flex: 1, + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: Card( + color: _config.theme == "textured-dark" || + _config.theme == "textured-light" + ? Colors.transparent + : null, + elevation: 4, // Add elevation for shadow + shadowColor: _config.theme == "textured-dark" || + _config.theme == "textured-light" + ? Colors.transparent + : null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Online Mode', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 16), // Add spacing if needed + Center( + child: Consumer<AppState>( + builder: (context, appState, _) { + return OnlineModeChoice( + onModeChanged: (mode) { + toggleOnlineMode(context, mode); + print(mode); + }, + theme: _config.theme, + ); }, - theme: _config.theme, - ); - }, - ), + ), + ), + ], ), - ], + ), ), ), ), ), - ), + ], ), + + ], ), SizedBox(height: 15), diff --git a/lib/screens/loading_screen.dart b/lib/screens/loading_screen.dart new file mode 100644 index 0000000..f4d6c6d --- /dev/null +++ b/lib/screens/loading_screen.dart @@ -0,0 +1,81 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_voiceassistant/screens/home_screen.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../models/app_state.dart'; +import '../utils/app_config.dart'; + +class Loading extends StatelessWidget { + final AppConfig config; + final String wakeWord; + Loading({required this.config, required this.wakeWord}); + + Future<void> initialize(BuildContext context) async { + // Initialize the app + final appState = context.read<AppState>(); + bool isWakeWordMode = false; + String intentEngine = "snips"; + String streamId = ""; + bool isCommandProcessing = false; + String commandProcessingText = "Processing..."; + String sttFramework = "vosk"; + bool onlineMode = false; + + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.reload(); + if(prefs.containsKey('isWakeWordMode')) { + isWakeWordMode = await prefs.getBool('isWakeWordMode')!; + } + if(prefs.containsKey('intentEngine')) { + intentEngine = await prefs.getString('intentEngine')!; + } + if(prefs.containsKey('streamId')) { + streamId = await prefs.getString('streamId')!; + } + if(prefs.containsKey('isCommandProcessing')) { + isCommandProcessing = await prefs.getBool('isCommandProcessing')!; + } + if(prefs.containsKey('commandProcessingText')) { + commandProcessingText = await prefs.getString('commandProcessingText')!; + } + if(prefs.containsKey('sttFramework')) { + sttFramework = await prefs.getString('sttFramework')!; + } + if(prefs.containsKey('onlineMode')) { + onlineMode = await prefs.getBool('onlineMode')!; + } + + appState.isWakeWordMode = isWakeWordMode; + appState.intentEngine = intentEngine; + appState.streamId = streamId; + appState.isCommandProcessing = isCommandProcessing; + appState.commandProcessingText = commandProcessingText; + appState.sttFramework = sttFramework; + appState.onlineMode = onlineMode; + + print('isWakeWordMode: $isWakeWordMode'); + print('intentEngine: $intentEngine'); + print('streamId: $streamId'); + print('isCommandProcessing: $isCommandProcessing'); + print('commandProcessingText: $commandProcessingText'); + print('sttFramework: $sttFramework'); + print('onlineMode: $onlineMode'); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: initialize(context), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return HomePage(config: config, wakeWord: wakeWord); + } else { + return CupertinoActivityIndicator(); + } + }, + ); + } +} |