frontend/frontend-flutter/lib/main.dart (2,576 lines of code) (raw):

import 'dart:ui' as ui; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:simple_gradient_text/simple_gradient_text.dart'; import 'package:ttmd/screens/bot.dart'; import 'package:ttmd/screens/disclaimer.dart'; import 'package:ttmd/screens/settings.dart' as ts; import 'package:ttmd/services/display_stepper/display_stepper_cubit.dart'; import 'package:ttmd/services/display_stepper/display_stepper_state.dart'; import 'package:ttmd/services/first_question/first_question_cubit.dart'; import 'package:ttmd/services/first_question/first_question_state.dart'; import 'package:ttmd/services/load_question/load_question_cubit.dart'; import 'dart:async'; import 'dart:convert'; import 'package:easy_sidemenu/easy_sidemenu.dart'; import 'package:expandable_tree_menu/expandable_tree_menu.dart'; import 'package:ttmd/services/load_question/load_question_state.dart'; import 'package:intl/intl.dart'; import 'package:badges/badges.dart' as badges; import 'package:ttmd/services/new_suggestions/new_suggestion_cubit.dart'; import 'package:ttmd/services/new_suggestions/new_suggestion_state.dart'; import 'package:ttmd/services/update_expert_mode/update_expert_mode_cubit.dart'; import 'package:ttmd/services/update_popular_questions/update_popular_questions_cubit.dart'; import 'package:ttmd/services/update_popular_questions/update_popular_questions_state.dart'; import 'package:ttmd/services/update_stepper/update_stepper_cubit.dart'; import 'package:ttmd/services/update_stepper/update_stepper_state.dart'; import 'package:ttmd/utils/TextToDocParameter.dart'; import 'package:ttmd/utils/most_popular_questions.dart'; import 'package:ttmd/utils/pdf_viewer.dart'; import 'package:ttmd/utils/stepper_expert_info.dart'; import 'package:expansion_tile_card/expansion_tile_card.dart'; import 'package:flutter/services.dart'; import 'package:flutter_json_viewer/flutter_json_viewer.dart'; import 'dart:html' as html; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:csv/csv.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/rendering.dart'; late final FirebaseApp app; late final FirebaseAuth auth; late User currentUser; late String LastName; late String userID; late FirebaseFirestore db; final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); Map<String, Widget Function(BuildContext)> myRoutes = { '/landingPage': (context) => ContentTtmd(title: 'Open data QnA'), //'/pdfViewer': (context) => PdfViewer(), '/settings': (context) => ts.Settings(db), }; void main() async { WidgetsFlutterBinding.ensureInitialized(); /*app = await Firebase.initializeApp( name: 'opendataqna', options: DefaultFirebaseOptions.web, // currentPlatform, );*/ app = await Firebase.initializeApp(options: DefaultFirebaseOptions.web); auth = FirebaseAuth.instanceFor(app: app); /*db = await FirebaseFirestore.instanceFor( app: app, databaseId: 'opendataqna-session-logs');*/ db = await FirebaseFirestore.instanceFor(app: app); print('Main: main() : auth = $auth'); print('Main: main() : db = $db'); print('Main: main() : db.databaseId = ${db.databaseId}'); //FirebaseAuth.instance auth.authStateChanges().listen((User? user) { if (user != null) { print("Main : user.uid = ${user.uid}"); currentUser = user; } }); runApp(ttmd()); } class ttmd extends StatelessWidget { ttmd({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider( create: (context) => LoadQuestionCubit(), ), BlocProvider( create: (context) => FirstQuestionCubit(), ), BlocProvider( create: (context) => UpdatePopularQuestionsCubit(), ), BlocProvider( create: (context) => UpdateStepperCubit(), ), BlocProvider( create: (context) => NewSuggestionCubit(), ), BlocProvider( create: (context) => DisplayStepperCubit(), ), BlocProvider( create: (context) => UpdateExpertModeCubit(), ), ], child: MaterialApp( debugShowCheckedModeBanner: false, //navigatorKey: navigatorKey, title: 'Open data QnA', theme: ThemeData( appBarTheme: AppBarTheme( backgroundColor: Colors.white, ), checkboxTheme: CheckboxThemeData( fillColor: WidgetStateProperty.resolveWith((states) { if (!states.contains(WidgetState.selected)) { return Colors.white; } return Colors.green; }), checkColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return Colors.red; } return null; }))), onGenerateRoute: (settings) { print('Main: ttmd : build() : onGenerateRoute : START'); if (settings.name == '/landingPage' && !TextToDocParameter.isAuthenticated) { print( 'Main: ttmd : build() : onGenerateRoute : attempting to access /landingPage without authentication'); return MaterialPageRoute( //builder: (context) => LoginScreen(), builder: (context) => Disclaimer(auth), ); } else if (settings.name == '/pdfViewer' && !TextToDocParameter.isAuthenticated) { print( 'Main: ttmd : build() : onGenerateRoute : attempting to access /pdfViewer without authentication'); return MaterialPageRoute( //builder: (context) => LoginScreen(), builder: (context) => Disclaimer(auth), ); } else if (settings.name == '/settings' && !TextToDocParameter.isAuthenticated) { print( 'Main: ttmd : build() : onGenerateRoute : attempting to access /settings without authentication'); return MaterialPageRoute( //builder: (context) => LoginScreen(), builder: (context) => Disclaimer(auth), ); } else { print( 'Main: ttmd : build() : onGenerateRoute : attempting to access ${settings.name!} with proper authentication'); if (settings.name == '/pdfViewer') { final args = settings.arguments as Map; return MaterialPageRoute( builder: (context) { return PdfViewer(bytes: args["bytes"]); }, ); } else { return MaterialPageRoute( builder: myRoutes[settings.name!]!, ); } } // Let the default routing handle other routes }, initialRoute: '/', home: Disclaimer(auth), ), ); } } class ContentTtmd extends StatefulWidget { const ContentTtmd({super.key, required this.title}); final String title; @override State<ContentTtmd> createState() => _ContentTtmdState(); } class _ContentTtmdState extends State<ContentTtmd> { late GlobalKey<BotState> _botKey; int _selectedIndex = 0; int _selectedIndexNavRail = 0; PageController pageController = PageController(); SideMenuController sideMenu = SideMenuController(); SideMenuExpansionItem? historySideMenuExpansionItem; List<SideMenuItem>? childrenHistorySideMenuItem; Map<String, MostPopularQ>? mostPopularQuestionsMap = {}; int currentStep = 0; List<StepperExpertInfo> stepperExpertInfoList = <StepperExpertInfo>[]; bool isFirstQuestionStatus = true; List<TreeNode<dynamic>> nodes = []; final List<String> _destinations = [ 'New Chat', 'History', 'Most popular questions', 'Summary Extract', //'Help', 'Settings' ]; bool _isExpanded = false; bool _isQuestionExpanded = false; Size? screenSize; bool _isFirstQuestionNotAskedYet = true; TextEditingController textEditingController = TextEditingController(); Bot? bot; double overallProcessingTime = 0; bool useExpertMode = false; bool isTextToDoC = false; bool isTextToDoC1 = false; bool light = true; Container? suggestionContainer; List<TreeNode<String>> importedQuestionTreeNodeList = [ TreeNode("No questions available") ]; InAppWebViewController? webViewController; //late FirebaseFirestore db; List<String> userGroupingList = <String>[]; String selectedValue = ""; final ValueNotifier<bool> updateSelection = ValueNotifier(false); final ValueNotifier<bool> importedQuestionNotifier = ValueNotifier(false); final ValueNotifier<String?> selectedValueNotifier = ValueNotifier<String?>(null); //final TextEditingController _dropdownController = TextEditingController(); List<TreeNode<String>> CreateNodesMostPopularQuestion( List<MostPopularQ> mostPopularQuestionsList) { List<TreeNode<String>> nodes = []; for (int i = 0; i < mostPopularQuestionsList.length; i++) { if (mostPopularQuestionsList[i].question.length != 0) nodes.add(TreeNode<String>(mostPopularQuestionsList[i].time + "|||" + mostPopularQuestionsList[i].question + "|||" + mostPopularQuestionsList[i].count.toString())); } return nodes; } void _addData(String data, bool isHistory) { List<MostPopularQ>? mostPopularQuestionsList = <MostPopularQ>[]; int lenghtTmp = 0; String timeString = ""; timeString = displayDateTime(); if (data.length == 0) return; print("Main: _addData : BEFORE ADD : nodes.length = ${nodes.length}"); print("Main: _addData : BEFORE ADD : data = ${data}"); print("Main: _addData : BEFORE ADD : nodes = ${nodes}"); if (isHistory) nodes.add(TreeNode(timeString + "|||" + data)); //displayDateTime() else nodes.add(TreeNode(data)); //add questions to mostPopularQuestionsMap if (mostPopularQuestionsMap!.containsKey(data)) { print('Main: _addData : mostPopularQuestionsMap contains $data'); print( 'Main: _addData : mostPopularQuestionsMap : BEFORE INCREMENT : (mostPopularQuestionsMap![data] as MostPopularQ).count = ${(mostPopularQuestionsMap![data] as MostPopularQ).count}'); mostPopularQuestionsMap![data]!.count = mostPopularQuestionsMap![data]!.count + 1; mostPopularQuestionsMap![data]!.time = timeString; print( 'Main: _addData : mostPopularQuestionsMap : AFTER INCREMENT : (mostPopularQuestionsMap![data] as MostPopularQ).count = ${(mostPopularQuestionsMap![data] as MostPopularQ).count}'); } else { print('Main: _addData : mostPopularQuestionsMap does not contain $data'); mostPopularQuestionsMap![data] = MostPopularQ(data, 1, timeString); } var sortedByValueMap = Map.fromEntries( mostPopularQuestionsMap!.entries.toList() ..sort((e1, e2) => e2.value.count.compareTo(e1.value.count))); print( 'Main: _addData : sortedByValueMap.length = ${sortedByValueMap.length}'); print('Main: _addData : sortedByValueMap = $sortedByValueMap'); int countEntries = 0; for (var entry in sortedByValueMap.entries) { mostPopularQuestionsList!.add(entry.value); if (countEntries == 2 || countEntries == sortedByValueMap.length - 1) break; countEntries++; } //countEntries = 0; print( "Main: _addData : BEFORE FILLING : mostPopularQuestionsList.length = ${mostPopularQuestionsList.length}"); print( "Main: _addData : BEFORE FILLING : mostPopularQuestionsList = ${mostPopularQuestionsList}"); lenghtTmp = mostPopularQuestionsList.length; //Fill mostPopularQuestionsList with dummy entries if there are not 3 most popular questions for (int i = 0; i <= (2 - lenghtTmp); i++) { mostPopularQuestionsList!.add(MostPopularQ("", 0, "")); } print( "Main: _addData : AFTER FILLING : mostPopularQuestionsList.length = ${mostPopularQuestionsList.length}"); print( "Main: _addData : AFTER FILLING : mostPopularQuestionsList = ${mostPopularQuestionsList}"); //Update the 3 most popular questions BlocProvider.of<UpdatePopularQuestionsCubit>(context) .updateMostPopularQuestions( mostPopularQuestionsList: mostPopularQuestionsList!, time: timeString); print("Main: _addData : AFTER : nodes.length = ${nodes.length}"); print("Main: _addData : AFTER : nodes = ${nodes}"); } void _nodeSelected(context, nodeValue) { int scenarioNumber = 0; String questionTmp = ""; print('Main : _nodeSelected() : START'); print( 'Main : _nodeSelected() : nodeValue.toString() = ${nodeValue.toString()}'); questionTmp = nodeValue.toString().split(":")[0]; print('Main : _nodeSelected() : questionTmp = $questionTmp'); if (questionTmp.length <= 2) { //we don't expect to have more than 99 questions, so 2 digits are enough scenarioNumber = int.parse(questionTmp); print( 'Main : _nodeSelected() : questionTmp.length <=2 : scenarioNumber = $scenarioNumber'); } if (nodeValue.toString().contains(":")) { print('Main : _nodeSelected() : nodeValue.toString().contains(":")'); //scenario#:question:genre:user_grouping in the future, may add a 5th element : main_question print( 'Main : _nodeSelected() : nodeValue.toString().contains(":") : nodeValue.toString().split(":")[2] = ${nodeValue.toString().split(":")[2]}'); textEditingController.text = nodeValue.toString().split(":")[1]; BlocProvider.of<NewSuggestionCubit>(context).generateNewSuggestions( int.parse(nodeValue.toString().split(":")[0]), textEditingController.text, isACannedQuestion: false, userGrouping: nodeValue.toString().split(":")[3]); print( "Main: _nodeSelected() : textEditingController.text = = ${textEditingController.text}"); //setting on the UI the current user_grouping based on the question clicked assuming next question is for the same user_grouping selectedValueNotifier.value = nodeValue.toString().split(":")[3]; TextToDocParameter.currentUserGrouping = nodeValue.toString().split(":")[3]; TextToDocParameter.currentScenarioName = nodeValue.toString().split(":")[2]; } else return; //textEditingController.text = nodeValue.toString(); } void _nodeSelected1(context, nodeValue) { var val = nodeValue.toString().split("|||"); textEditingController.text = val[1]; /*final route = MaterialPageRoute(builder: (context) => DetailPage(value: nodeValue)); Navigator.of(context).push(route);*/ } /// Build the Node widget at a specific node in the tree Widget _nodeBuilder(context, nodeValue) { return Card( margin: EdgeInsets.symmetric(vertical: 1), child: Padding( padding: const EdgeInsets.all(8.0), child: Text(nodeValue.toString()), )); } Widget _nodeBuilder2(context, nodeValue) { String? badgeCount; String scenarioTitle = ""; String question = ""; List<String> tmpList = []; print("Main : _nodeBuilder2() : START"); print("Main : _nodeBuilder2() : nodeValue = $nodeValue"); if (nodeValue.toString().contains(":")) { //question#: question tmpList = nodeValue.toString().split(":"); scenarioTitle = tmpList[2]; //"Scenario " + tmpList[0]; question = tmpList[1]; } else if (nodeValue.toString().contains(" - ")) { //Scenario x - user_grouping -#of questions tmpList = nodeValue.toString().split(" - "); badgeCount = tmpList[2]; question = tmpList[0] + ' - ' + tmpList[1]; } bool isScenario = nodeValue.toString().contains("Scenario"); print("Main : _nodeBuilder2() : scenarioTitle = $scenarioTitle"); print("Main : _nodeBuilder2() : question = $question"); return Card( margin: EdgeInsets.symmetric(vertical: 1), child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ ListTile( leading: Padding( padding: const EdgeInsets.only(right: 9.0), child: !isScenario ? CircleAvatar( backgroundColor: Colors.green, radius: 12, child: Text("Q${TextToDocParameter.questionCount++}", style: TextStyle(color: Colors.white, fontSize: 9))) : badges.Badge( badgeContent: Text(badgeCount!), child: Icon(Icons.account_tree_sharp), ), ), title: !isScenario ? Text(scenarioTitle, style: TextStyle(fontSize: 9, color: Colors.indigoAccent)) : null, subtitle: !isScenario ? Text(question) : Text(" " + question, style: TextStyle(fontWeight: FontWeight.bold)), ), ], )); } Widget _nodeBuilder1(context, nodeValue) { var val = nodeValue.toString().split("|||"); return Card( margin: EdgeInsets.symmetric(vertical: 1), child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ ListTile( leading: Padding( padding: const EdgeInsets.only(right: 10.0), child: CircleAvatar( backgroundColor: Colors.green, radius: 12, child: Icon(Icons.question_mark, size: 10, color: Colors.white)), ), title: Text(val[0], style: TextStyle(fontSize: 8, color: Colors.indigoAccent)), subtitle: Text(val[1]), ), ], )); } Widget _nodeBuilder3(context, nodeValue) { var val = nodeValue.toString().split("|||"); //val[0] => timestamp //val[1] => question //val[2] => Count return Card( margin: EdgeInsets.symmetric(vertical: 1), child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ ListTile( leading: Padding( padding: const EdgeInsets.only(right: 20.0), child: /*2, child: Icon(Icons.question_mark, size: 10, color: Colors.white))*/ badges.Badge( badgeContent: Text(val[2]), child: Icon(Icons.question_mark), ), ), title: Text(val[0], style: TextStyle(fontSize: 8, color: Colors.indigoAccent)), subtitle: Text(val[1]), ), ], )); } FutureBuilder<List<List<dynamic>>?> _createSideMenu() { print('Main : _createSideMenu() : START'); TextToDocParameter.questionCount = 1; return FutureBuilder( future: loadQuestionsFromFirestore(), builder: (context, snapshot) { return SideMenu( controller: sideMenu, style: SideMenuStyle( // showTooltip: false, displayMode: SideMenuDisplayMode.open, showHamburger: true, hoverColor: Colors.blue[100], selectedHoverColor: Colors.blue[100], selectedColor: Colors.lightBlue, selectedTitleTextStyle: const TextStyle(color: Colors.black), selectedIconColor: Colors.white, ), title: Column( children: [ ConstrainedBox( constraints: const BoxConstraints( maxHeight: 100, maxWidth: 250, ), child: Image.asset( 'assets/images/ttmd_logo1.png', //'assets/images/drawer_header1.png', ), ), const Divider( indent: 8.0, endIndent: 8.0, ), ], ), footer: Padding( padding: const EdgeInsets.only(top: 8.0), child: ConstrainedBox( constraints: const BoxConstraints( maxHeight: 80, ), child: Container( color: const Color.fromARGB( 255, 240, 240, 240), // Colors.grey.withOpacity(1),//// child: Column( children: [ const Divider( indent: 8.0, endIndent: 8.0, ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.max, children: [ CircleAvatar( backgroundImage: //const AssetImage('assets/images/john_smith.jpeg'), NetworkImage(TextToDocParameter.picture), radius: 30, ), Text("${TextToDocParameter.email}", style: TextStyle( color: Colors.blueAccent, fontSize: 12.0), textAlign: TextAlign.end), ], ), ], ), ), ), ), items: [ SideMenuItem( title: 'Open data QnA', onTap: (index, _) { //sideMenu.changePage(index); }, icon: const Icon(Icons.home), tooltipContent: "Open data QnA", ), SideMenuItem( title: 'New chat', onTap: (index, _) { TextToDocParameter.sessionId = ""; print( "Main: _createSideMenu() : New chat : : TextToDocParameter.sessionId = ${TextToDocParameter.sessionId}"); }, icon: const Icon(Icons.restart_alt_outlined), ), if (snapshot.hasData) //if (TextToDocParameter.expert_mode) SideMenuExpansionItem( title: "Imported questions", icon: const Icon(Icons.manage_history_outlined), children: [ SideMenuItem( builder: (context, displayMode) { return ExpandableTree( nodes: importedQuestionTreeNodeList, nodeBuilder: _nodeBuilder2, onSelect: (node) => _nodeSelected(context, node), ); }, onTap: (index, _) { //sideMenu.changePage(index); }, ) ], ), SideMenuExpansionItem( title: "History", icon: const Icon(Icons.manage_history_outlined), children: [ SideMenuItem( builder: (context, displayMode) { return BlocBuilder<LoadQuestionCubit, LoadQuestionState>( builder: (context, state) { print( "Main: _createSideMenu() : BlocBuilder<LoadQuestionCubit, LoadQuestionState> : state = $state"); print( "Main: _createSideMenu() : BlocBuilder<LoadQuestionCubit, LoadQuestionState> : state.question = ${state.question}"); print( "Main: _createSideMenu() : BlocBuilder<LoadQuestionCubit, LoadQuestionState> : state.time = ${state.time}"); _addData(state.question!, true); return ExpandableTree( nodes: nodes, nodeBuilder: _nodeBuilder1, onSelect: (node) => _nodeSelected1(context, node), ); }, ); }, onTap: (index, _) { //sideMenu.changePage(index); }, ) ], ), SideMenuExpansionItem( title: "Most popular questions", icon: const Icon(Icons.manage_history_outlined), children: [ SideMenuItem( builder: (context, displayMode) { return BlocBuilder<UpdatePopularQuestionsCubit, UpdatePopularQuestionsState>( builder: (context, state) { print( "Main: _createSideMenu() : Most popular questions : BlocBuilder<UpdatePopularQuestionsCubit,UpdatePopularQuestionsState> : state.status = ${state.status}"); print( "Main: _createSideMenu() : Most popular questions : BlocBuilder<UpdatePopularQuestionsCubit,UpdatePopularQuestionsState> : state.mostPopularQuestionsList = ${state.mostPopularQuestionsList}"); //_addData(state.question!, true); List<TreeNode<String>> nodesMostPopularQuestions = CreateNodesMostPopularQuestion( state.mostPopularQuestionsList!); return ExpandableTree( nodes: nodesMostPopularQuestions, nodeBuilder: _nodeBuilder3, onSelect: (node) => _nodeSelected1(context, node), ); }, ); }, onTap: (index, _) { //sideMenu.changePage(index); }, ) ], ), SideMenuItem( title: 'Import', onTap: (index, _) async { var questionList = await importQuestions(); if (questionList.length == 0) { print( "Main: _createSideMenu() : SideMenuItem : Import : onTap() : questionList is empty"); return; } //sort the list of list based on the name of the database : //grouping, scenario, question //Remove the header questionList.removeAt(0); questionList.sort((a, b) => a.first.compareTo(b.first)); //Save questions into SaveImportedQuestionsToFirestore(questionList); for (var entry in questionList) print( "Main: _createSideMenu() : SideMenuItem : Import : onTap() : sorted questionList = ${entry[0]}, ${entry[2]}"); setState(() { importedQuestionTreeNodeList = createQuestionList(questionList); //print("Main: _createSideMenu() : SideMenuItem : Import : setState() : importedQuestionTreeNodeList = ${importedQuestionTreeNodeList}"); }); const snackBar = SnackBar( content: Text('Importing questions.'), duration: Duration(seconds: 3), ); ScaffoldMessenger.of(context).showSnackBar(snackBar); }, icon: const Icon(Icons.file_upload_outlined), ), /* //Commented out because the export of the answers (images and tables) to a pdf is not implemented yet SideMenuItem( title: 'Export', onTap: (index, _) { _createPDF(); const snackBar = SnackBar( content: Text('Exporting data to a pdf file.'), duration: Duration(seconds: 5), ); ScaffoldMessenger.of(context).showSnackBar(snackBar); }, icon: const Icon(Icons.import_export_outlined), ),*/ /*SideMenuItem( // do not delete title: 'Clear Firestore history', onTap: (index, _) async { int count = 0; //get all the documents corresponding to the user id try { var querySnapshot = await db .collection("session_logs") .where("user_id", isEqualTo: TextToDocParameter.userID) .get(); print( "Main: _createSideMenu() : Clear Firestore history : querySnapshot.docs.length = ${querySnapshot.docs.length}"); print( "Main: _createSideMenu() : Clear Firestore history : querySnapshot = ${querySnapshot}"); //delete all these documents //await db.collection("session_logs").doc('FLoOKBwvVJ8mXkzu06He').delete(); for (var docSnapshot in querySnapshot.docs) { print( 'Main: _createSideMenu() : Clear Firestore history : ${docSnapshot.id} => ${docSnapshot.data()}'); print( 'Main: _createSideMenu() : Clear Firestore history : docSnapshot.reference ${docSnapshot.reference}'); db .collection("session_logs") .doc('${docSnapshot.id}') .delete(); count++; } } catch (e) { print( 'Main: _createSideMenu() : Clear Firestore history : EXCEPTION : $e'); } var snackBar = SnackBar( content: Text('Deleted $count questions from Firestore'), duration: Duration(seconds: 3), ); ScaffoldMessenger.of(context).showSnackBar(snackBar); }, icon: const Icon(Icons.auto_delete_outlined), ),*/ SideMenuItem( title: 'Settings', onTap: (index, _) async { await Navigator.pushNamed(context, '/settings', arguments: {"dummy": ""}); print( "Main : createSideMenu() : TextToDocParameter.expert_mode = ${TextToDocParameter.expert_mode}"); setState(() { //useExpertMode = ts.Settings.isExpert; //useExpertMode = TextToDocParameter.expert_mode; TextToDocParameter.expert_mode; }); //GalleryScreen }, icon: const Icon(Icons.settings), ), ], ); }, )!; } @override void initState() { super.initState(); setup(); } Future<void> setup() async { _botKey = GlobalKey(); bot = Bot(key: _botKey, textEditingController: textEditingController, db: db); //Initialize stepper states BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded( status: StepperStatus.initial, message: "Please enter a question.", stateStepper: StepState.disabled, isActiveStepper: false); print( "main: setup() : After BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded() : stepper initialized"); } Future<void> initializeFirestore() async { await loadCfgFromFirestore(); //await loadQuestionsFromFirestore(); } Future<List<List<dynamic>>?> loadQuestionsFromFirestore() async { print('Main: loadQuestionsFromFirestore() : START'); List<List<dynamic>>? questionList = []; print( 'Main: loadQuestionsFromFirestore() : TextToDocParameter.imported_questions = ${TextToDocParameter.imported_questions}'); print( 'Main: loadQuestionsFromFirestore() : TextToDocParameter.userID = ${TextToDocParameter.userID}'); try { var querySnapshot = await db! .collection("imported_questions") .where("user_id", isEqualTo: TextToDocParameter.userID) .orderBy('scenario', descending: false) .orderBy('order') .get(); for (var docSnapshot in querySnapshot.docs) { final data = docSnapshot.data() as Map<String, dynamic>; print( 'Main: loadQuestionsFromFirestore() : data["user_grouping"] = ${data["user_grouping"]} : data["scenario"] = ${data["scenario"]} : data["question"] = ${data["question"]}'); questionList.add([ data["user_grouping"], data["scenario"], data["question"], data["main_question"] ]); } print( 'Main: loadQuestionsFromFirestore() : questionList.length = ${questionList.length}'); print( 'Main: loadQuestionsFromFirestore() : questionList = ${questionList}'); //create the questionList from Firestore if (questionList.length == 0) questionList = null; else importedQuestionTreeNodeList = createQuestionList(questionList); /*BlocProvider.of<UpdateExpertModeCubit>(context) .updateExpertMode(TextToDocParameter.expert_mode);*/ } catch (e) { print('Main: loadQuestionsFromFirestore() : EXCEPTION : e = $e'); } finally { return questionList; } } Future<void> loadCfgFromFirestore() async { /*db = await FirebaseFirestore.instanceFor( app: app, databaseId: 'opendataqna-session-logs');*/ print("main: loadCfgFromFirestore() : db = $db"); if (TextToDocParameter.userID.isEmpty) { print( "main: loadCfgFromFirestore() : TextToDocParameter.userID is empty = ${TextToDocParameter.userID}"); WidgetsBinding.instance.addPostFrameCallback((_) { noCfgStoredinFirestore(); }); return; } try { print( "main: loadCfgFromFirestore() : TextToDocParameter.userID = ${TextToDocParameter.userID}"); DocumentSnapshot doc = await db! .collection("front_end_flutter_cfg") .doc('${TextToDocParameter.userID}') .get(); if (doc != null) { final data = doc.data() as Map<String, dynamic>; TextToDocParameter.anonymized_data = data["anonymized_data"]; TextToDocParameter.expert_mode = data["expert_mode"]; TextToDocParameter.endpoint_opendataqnq = data["endpoint_opendataqnq"]; TextToDocParameter.firestore_database_id = data["firestore_database_id"]; TextToDocParameter.firebase_app_name = data["firebase_app_name"]; TextToDocParameter.firestore_history_collection = data["firestore_history_collection"]; TextToDocParameter.firestore_cfg_collection = data["firestore_cfg_collection"]; TextToDocParameter.imported_questions = data["imported_questions"]; print( "main: loadCfgFromFirestore() : TextToDocParameter.anonymized_data = ${TextToDocParameter.anonymized_data}"); print( "main: loadCfgFromFirestore() : TextToDocParameter.expert_mode = ${TextToDocParameter.expert_mode}"); print( "main: loadCfgFromFirestore() : TextToDocParameter.firestore_database_id = ${TextToDocParameter.firestore_database_id}"); print( "main: loadCfgFromFirestore() : TextToDocParameter.endpoint_opendataqnq = ${TextToDocParameter.endpoint_opendataqnq}"); print( "main: loadCfgFromFirestore() : TextToDocParameter.firebase_app_name = ${TextToDocParameter.firebase_app_name}"); print( "main: loadCfgFromFirestore() : TextToDocParameter.firestore_history_collection = ${TextToDocParameter.firestore_history_collection}"); print( "main: loadCfgFromFirestore() : TextToDocParameter.firestore_cfg_collection = ${TextToDocParameter.firestore_cfg_collection}"); print( "main: loadCfgFromFirestore() : TextToDocParameter.imported_questions = ${TextToDocParameter.imported_questions}"); BlocProvider.of<DisplayStepperCubit>(context) .displayStepper(TextToDocParameter.expert_mode); } else { print("main: loadCfgFromFirestore() : doc == null"); WidgetsBinding.instance.addPostFrameCallback((_) { noCfgStoredinFirestore(); }); } } catch (e) { print("main: loadCfgFromFirestore() : EXCEPTION ON FIRESTORE : e = $e"); WidgetsBinding.instance.addPostFrameCallback((_) { noCfgStoredinFirestore(); }); } } Future<List<String>> _getUserGrouping() async { print('Main : _getUserGrouping() : START'); List<String> resp = []; await initializeFirestore(); print('Main : _getUserGrouping() : bot = $bot'); Map<String, String>? _headers = { "Content-Type": "application/json", //"Authorization": " Bearer ${client!.credentials.accessToken.toString()}", }; try { print( 'Main : _getUserGrouping() : url = ${TextToDocParameter.endpoint_opendataqnq}/available_databases'); var response = await html.HttpRequest.requestCrossOrigin( '${TextToDocParameter.endpoint_opendataqnq}/available_databases', method: "GET"); print('Main : _getUserGrouping() : response = ' + response.toString()); final jsonData = jsonDecode(response); if (jsonData != null) { print('Main : _getUserGrouping() : jsonData = $jsonData'); /* Expected response : { "Error": "", "KnownDB": "[{\"table_schema\":\"imdb-postgres\"},{\"table_schema\":\"retail-postgres\"}]", "ResponseCode": 200 }*/ var knownSqlMap = jsonDecode(jsonData['KnownDB']); print('Main : _getUserGrouping() : knownSqlMap = ${knownSqlMap}'); print( 'Main : _getUserGrouping() : knownSqlMap[0] = ${knownSqlMap[0].toString()}'); for (int i = 0; i < knownSqlMap.length; i++) { for (var entry in knownSqlMap[i].entries) { print('${entry.key} : ${entry.value}'); if (entry.key == "table_schema") resp.add(entry.value); } } } else { resp.add(""); } } catch (e) { print('Main : _getUserGrouping() : EXCEPTION = $e'); throw Exception('Failed to get earning calls question suggestions: $e'); } finally { print('Main : _getUserGrouping() : resp = $resp'); return resp; } } Future<List<Map<String, dynamic>>> _getQuestions() async { var questionList = await _getLastQuestions(); print("Main : _getQuestions() : questionList = $questionList"); return questionList; } @override Widget build(BuildContext context) { debugPaintSizeEnabled = false; screenSize = MediaQuery.of(context).size; bool _isEarningCalls1Hovered = false; bool _isEarningCalls2Hovered = false; bool _isBookingAnalysisHovered = false; bool _isFunelAnalysisHovered = false; print("Main : build() : START"); print("Main : build() : TuserGroupingList = ${userGroupingList}"); print( "Main : build() : TextToDocParameter.currentUserGrouping = ${TextToDocParameter.currentUserGrouping}"); return Scaffold( appBar: AppBar( title: Image.asset('assets/images/cymbal_logo.png', scale: 2), centerTitle: false, actions: [ Padding( padding: const EdgeInsets.only(right: 100), child: Container( width: 320, child: FutureBuilder( future: _getUserGrouping(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Padding( padding: const EdgeInsets.only(right: 135.0, left: 135.0), child: Container( width: 50, height: 50, child: /*Text('TEST')*/ CircularProgressIndicator()), ); // Loading indicator } else if (snapshot.hasError) { return Text('-Error-: ${snapshot.error}'); // Error handling } else { if (snapshot.data!.isNotEmpty) { TextToDocParameter.currentUserGrouping = snapshot.data![0]; TextToDocParameter.userGroupingList = snapshot.data!; selectedValueNotifier.value = snapshot.data!.first; print( "main: build() : TextToDocParameter.currentUserGrouping = ${TextToDocParameter.currentUserGrouping}"); print("main: build() : selectedValue = ${selectedValue}"); } return ValueListenableBuilder<String?>( valueListenable: selectedValueNotifier, builder: (context, value, child) { return Container( width: 300, child: Row( children: [ Text("User Grouping: ", style: TextStyle( fontSize: 13.0, color: Colors.black)), Expanded( child: DropdownButton<String>( value: value, //icon: const Icon(Icons.arrow_downward), elevation: 16, style: const TextStyle( fontSize: 13.0, color: Colors.deepPurple), onChanged: (String? value) { selectedValueNotifier.value = value!; TextToDocParameter.currentUserGrouping = value!; print( 'Main: build() : DropdownButton : onChanged() : TextToDocParameter.currentUserGrouping => ${TextToDocParameter.currentUserGrouping}'); }, items: snapshot.data! .map<DropdownMenuItem<String>>( (String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), ), ), ], ), ); }); } }, ), ), ), ], leading: Text(""), ), body: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ _createSideMenu()!, const VerticalDivider( width: 0, ), Expanded( child: Stack( children: [ Container( padding: EdgeInsets.only(left: 100, right: 100), child: Column( mainAxisAlignment: MainAxisAlignment.start, //mainAxisSize: MainAxisSize.min, children: <Widget>[ BlocBuilder<DisplayStepperCubit, DisplayStepperState>( builder: (context, state) { print( "Main: build() : BlocBuilder<DisplayStepperCubit, DisplayStepperState> : START"); if (state.status == displayStepperStatus.display_stepper) { print( "Main: build() : BlocBuilder<DisplayStepperCubit, DisplayStepperState> : state =${state.status}"); return Flexible( fit: FlexFit.loose, child: Padding( padding: const EdgeInsets.only(bottom: 5), child: _buildStepper(), ), flex: 2); } else if (state.status == displayStepperStatus.remove_stepper) { print( "Main: build() : BlocBuilder<DisplayStepperCubit, DisplayStepperState> : state =${state.status}"); return Text(""); } else { print( "Main: build() : BlocBuilder<DisplayStepperCubit, DisplayStepperState> : ERROR : state =$state"); return Text("Unable to load user_grouping"); } }, ), bot!, BlocBuilder<NewSuggestionCubit, NewSuggestionState>( builder: (context, state) { if (state.status == NewSuggestionStateStatus.loaded) { print( "Main: build() : BlocBuilder<NewSuggestionCubit, NewSuggestionState> : START : state =$state"); return makeSuggestions( state.suggestionList!, state.scenarioNumber!); } else { return makeSuggestions(["x:", "x:", "x:"], 0); } }, ), //const Spacer(flex: 1,), ], ), ), BlocBuilder<FirstQuestionCubit, FirstQuestionState>( builder: (context, state) { print( "Main : build() : BlocBuilder<FirstQuestionCubit, FirstQuestionState> : START"); if (state.status == firstQuestionStatus.display_welcome_message) { print( "Main : build() : BlocBuilder<FirstQuestionCubit, FirstQuestionState> : state.status == firstQuestionStatus.display_welcome_message"); return Positioned( //top: 0, //left: 200, //200, child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Container(height: 100), Image.asset( //UNCOMMENT "assets/images/gemini.png"!, height: 90, width: 90, fit: BoxFit.cover, ), Padding( padding: const EdgeInsets.only(bottom: 50), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Hello ${TextToDocParameter.firstName} ! ", style: TextStyle( fontSize: 40.0, fontWeight: FontWeight.bold)), GradientText( 'How can I help you today ?', style: TextStyle( fontSize: 40.0, fontWeight: FontWeight.bold), colors: [ Colors.blue, Colors.red, Colors.teal, ], ), ], ), const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Learn more by selecting a card below\n', style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.normal, color: Colors.black)) ]) ], ), ), FutureBuilder( future: _getQuestions(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return CircularProgressIndicator(); // Loading indicator } else if (snapshot.hasError) { return Text('*Error*: ${snapshot.error}'); // Error handling } else { return Container( width: screenSize!.width / 1.5, child: Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ if (snapshot.data!.length >= 1) createCardsSuggestion( title: snapshot.data![0] ['scenario_name'] ?? "Error - No title", text: snapshot.data![0] ['user_question'] ?? "Error - No question", scenarioNumber: 2, isHovered: _isEarningCalls1Hovered, imagePath: "assets/images/last_questions.png", timeStamp: snapshot.data![0] ['timestamp'] ?? "Error - No timestamp", userGrouping: snapshot.data![0] ['user_grouping']), if (snapshot.data!.length >= 2) createCardsSuggestion( title: snapshot.data![1] ['scenario_name'] ?? "Error - No title", text: snapshot.data![1] ['user_question'] ?? "Error - No question", scenarioNumber: 2, isHovered: _isEarningCalls1Hovered, imagePath: "assets/images/last_questions.png", timeStamp: snapshot.data![1] ['timestamp'] ?? "Error - No timestamp", userGrouping: snapshot.data![0] ['user_grouping']), ], ), Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ if (snapshot.data!.length >= 3) createCardsSuggestion( title: snapshot.data![2][ 'scenario_name'] ?? "Error - No title", text: snapshot.data![2][ 'user_question'] ?? "Error - No question", scenarioNumber: 2, isHovered: _isEarningCalls1Hovered, imagePath: "assets/images/last_questions.png", timeStamp: snapshot .data![2] ['timestamp'] ?? "Error - No timestamp", userGrouping: snapshot.data![0] ['user_grouping']), if (snapshot.data!.length >= 4) createCardsSuggestion( title: snapshot.data![3][ 'scenario_name'] ?? "Error - No title", text: snapshot.data![3][ 'user_question'] ?? "Error - No question", scenarioNumber: 2, isHovered: _isEarningCalls1Hovered, imagePath: "assets/images/last_questions.png", timeStamp: snapshot .data![3] ['timestamp'] ?? "Error - No timestamp", userGrouping: snapshot.data![0] ['user_grouping']), ]) ], ), ); } }), //UNCOMMENT ], ), //UNCOMMENT ); } else { print( "Main : build() : BlocBuilder<FirstQuestionCubit, FirstQuestionState> : state.status != firstQuestionStatus.display_welcome_message"); isFirstQuestionStatus = false; return Text(""); } }, ), ], ), ), ], ), // This trailing comma makes auto-formatting nicer for build methods. ); } void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); } Future<List<int>> _createPDF() async { //Method not called because the export menu has been commented out. //This will remain commented out until it is possible to screenshot web views List<int> bytes = []; return bytes; } String displayDateTime() { String? dateTimeS; final now = DateTime.now(); dateTimeS = DateFormat('yyyy-MM-dd HH:mm:ss').format(now); return dateTimeS!; } Widget createCards( {required String? imagePath, required String? title, required String subTitle, required String text, required int badgeCount}) { return Expanded( child: Container( width: screenSize!.width / 3.5, //height: screenSize!.height / 6, child: Card( elevation: 10, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), // Define how the card's content should be clipped clipBehavior: Clip.antiAliasWithSaveLayer, // Define the child widget of the card child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ // Add padding around the row widget Padding( padding: const EdgeInsets.all(10), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ // Add an image widget to display an image Image.asset( imagePath!, height: 70, width: 70, fit: BoxFit.cover, ), // Add some spacing between the image and the text Container(width: 20), // Add an expanded widget to take up the remaining horizontal space Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ // Add some spacing between the top of the card and the title Container(height: 5), // Add a title widget Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title!, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF37474F)), ), badges.Badge( position: badges.BadgePosition.topEnd( top: -10, end: -12), showBadge: true, ignorePointer: false, onTap: () {}, badgeContent: Text( badgeCount < 10 ? " " + badgeCount.toString() : badgeCount.toString(), style: TextStyle( fontSize: 15, color: Colors .white)), //Icon(Icons.check, color: Colors.white, size: 14), badgeAnimation: badges.BadgeAnimation.rotation( animationDuration: Duration(seconds: 1), colorChangeAnimationDuration: Duration(seconds: 1), loopAnimation: false, curve: Curves.fastOutSlowIn, colorChangeAnimationCurve: Curves.easeInCubic, ), badgeStyle: badges.BadgeStyle( shape: badges.BadgeShape.instagram, badgeColor: Colors.red, //padding: EdgeInsets.all(5), borderRadius: BorderRadius.circular(4), borderSide: BorderSide(color: Colors.white, width: 2), elevation: 2, ), //child: Text('Badge'), ), ], ), // Add some spacing between the title and the subtitle Container(height: 5), // Add a subtitle widget Text( subTitle!, style: Theme.of(context) .textTheme .bodyMedium! .copyWith( color: Colors.grey[500], ), ), // Add some spacing between the subtitle and the text Container(height: 10), // Add a text widget to display some text Text( text.length <= 35 ? text! : text!.substring(0, 35) + ' ...', style: Theme.of(context) .textTheme .titleMedium! .copyWith( color: Colors.grey[700], ), maxLines: 1, ), Row(children: [ Spacer(), TextButton( style: TextButton.styleFrom( foregroundColor: Colors.transparent, ), child: const Text( "LOAD", style: TextStyle(color: Color(0xFFFF4081)), ), onPressed: () { print( 'Bot() : build() : TextButton : onPressed() : START : text = $text'); //BlocProvider.of<LoadQuestionCubit>(context).loadQuestionToChat("test"); //setState(() { textEditingController.text = text; _isFirstQuestionNotAskedYet = false; //}); }, ), ]) ], ), ), ], ), ), ], ), ), ), ); } Padding makeSuggestions( List<String> listRandomQuestions, int scenarioNumber) { print("Main: makeSuggestions() : START"); TextToDocParameter.lastScenarioNumber = scenarioNumber; return Padding( padding: const EdgeInsets.only(bottom: 20), child: Container( width: screenSize!.width, //height: screenSize!.height / 20, color: Colors.white, child: Row( mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ if (listRandomQuestions.length >= 1 && listRandomQuestions[0] != "x:") Expanded( child: ConstrainedBox( constraints: BoxConstraints( minWidth: 100.0, // Set minimum width maxWidth: 400.0, // Set maximum width ), child: InkWell( onTap: () { //textEditingController.text = "Can you provide the Top 10 pace platinum accounts with booking value and YOY growth %?"; textEditingController.text = listRandomQuestions[0].split(":")[1]; _isFirstQuestionNotAskedYet = false; BlocProvider.of<NewSuggestionCubit>(context) .generateNewSuggestions( scenarioNumber, textEditingController.text, isACannedQuestion: false); print( "Main: makeSuggestions() : textEditingController.text = = ${textEditingController.text}"); }, child: Card( elevation: 5, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: Padding( padding: const EdgeInsets.all(5.0), child: Row( children: [ Image.asset( "assets/images/suggestions.png", height: 20, width: 20, fit: BoxFit.cover, ), Flexible( child: Text( //' Can you provide the Top 10 pace platinum accounts with booking value and YOY growth %? ', listRandomQuestions[0].split(":")[1], style: TextStyle( decoration: TextDecoration.underline, fontSize: 15, ), softWrap: true, overflow: TextOverflow.ellipsis, maxLines: 2), ), ], ), ), ), ), ), ), //Container(width: 10), if (listRandomQuestions.length >= 2 && listRandomQuestions[1] != "x:") Expanded( child: ConstrainedBox( constraints: BoxConstraints( minWidth: 100.0, // Set minimum width maxWidth: 400.0, // Set maximum width ), child: InkWell( onTap: () { textEditingController.text = listRandomQuestions[1].split(":")[1]; _isFirstQuestionNotAskedYet = false; BlocProvider.of<NewSuggestionCubit>(context) .generateNewSuggestions( scenarioNumber, textEditingController.text, isACannedQuestion: false); print( "Main: makeSuggestions() : textEditingController.text = = ${textEditingController.text}"); }, child: Card( elevation: 5, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: Padding( padding: const EdgeInsets.all(5.0), child: Row( children: [ Image.asset( "assets/images/suggestions.png", height: 20, width: 20, fit: BoxFit.cover, ), Flexible( child: Text( listRandomQuestions[1].split(":")[1], style: TextStyle( decoration: TextDecoration.underline, fontSize: 15, ), softWrap: true, overflow: TextOverflow.ellipsis, maxLines: 2), ), ], ), ), ), ), ), ), //Container(width: 10), if (listRandomQuestions.length >= 3 && listRandomQuestions[2] != "x:") Expanded( child: ConstrainedBox( constraints: BoxConstraints( minWidth: 100.0, // Set minimum width maxWidth: 400.0, // Set maximum width ), child: InkWell( onTap: () { textEditingController.text = listRandomQuestions[2].split(":")[1]; _isFirstQuestionNotAskedYet = false; BlocProvider.of<NewSuggestionCubit>(context) .generateNewSuggestions( scenarioNumber, textEditingController.text, isACannedQuestion: false); print( "Main: makeSuggestions() : textEditingController.text = = ${textEditingController.text}"); }, child: Card( elevation: 5, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: Padding( padding: const EdgeInsets.all(5.0), child: Row( children: [ Image.asset( "assets/images/suggestions.png", height: 20, width: 20, fit: BoxFit.cover, ), Flexible( child: Text( //' Can you add CM sold margin to above list? ', listRandomQuestions[2].split(":")[1], style: TextStyle( decoration: TextDecoration.underline, fontSize: 15, ), softWrap: true, overflow: TextOverflow.ellipsis, maxLines: 2), ), ], ), ), ), ), ), ), ])), ); } Widget createCardsSuggestion( {required String? title, required String text, required int scenarioNumber, required bool isHovered, required String imagePath, required String timeStamp, required String userGrouping}) { print('Main : createCardsSuggestion() : START'); print('Main : createCardsSuggestion() : START : text = $text'); print('Main : createCardsSuggestion() : START : title = $title'); print('Main : createCardsSuggestion() : START : userGrouping = $userGrouping'); return Expanded( child: InkWell( onTap: () { print( 'Main : createCardsSuggestion() : TextButton : onPressed() : START : text = $text'); textEditingController.text = text; _isFirstQuestionNotAskedYet = false; //Use the user_grouping associated to the question //selectedValueNotifier.value = title; selectedValueNotifier.value = userGrouping; //Setting the current user_grouping value TextToDocParameter.currentUserGrouping = userGrouping; BlocProvider.of<NewSuggestionCubit>(context).generateNewSuggestions( scenarioNumber, text, isACannedQuestion: false, userGrouping: userGrouping); }, onHover: (value) { }, child: Container( width: screenSize!.width / 2.5, height: screenSize!.height / 6, child: Card( color: Colors.white, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), // Define how the card's content should be clipped clipBehavior: Clip.antiAliasWithSaveLayer, // Define the child widget of the card child: Padding( padding: const EdgeInsets.all(10), child: Row( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ Image.asset( imagePath!, height: 50, width: 50, fit: BoxFit.cover, ), Container(width: 20), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, //mainAxisSize: MainAxisSize.max, children: <Widget>[ // Add some spacing between the top of the card and the title Container(height: 5), // Add a title widget Flexible( //flex: 1, child: Container( color: isHovered ? Colors.blueGrey : null, child: Text( title!, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF37474F)), ), ), ), // Add some spacing between the title and the subtitle Container(height: 5), // Add some spacing between the subtitle and the text // Add a text widget to display some text Flexible( //flex: 6, child: Container( color: isHovered ? Colors.blueGrey : null, child: Text( text, style: Theme.of(context) .textTheme .titleMedium! .copyWith( color: Colors.grey[700], ), maxLines: 6, softWrap: true, overflow: TextOverflow.visible, ), ), ), Container(height: 10), Flexible( //flex: 6, child: Container( padding: EdgeInsets.only(top: 5), color: isHovered ? Colors.blueGrey : null, child: Text( 'Question typed on the ' + timeStamp, style: Theme.of(context) .textTheme .titleSmall! .copyWith( color: Colors.blueAccent, ), maxLines: 6, softWrap: true, overflow: TextOverflow.visible, ), ), ), ], ), ), ], ), ), ), ), ), ); } Widget _createDebugInfoCard(Widget leading, String title, String subtitle, String imageTrailingPath, Widget info, String infoText) { final ButtonStyle flatButtonStyle = TextButton.styleFrom( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(4.0)), ), ); return Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: ExpansionTileCard( //key: cardA, initiallyExpanded: true, leading: leading, title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ Flexible( flex: 5, child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)), ), Flexible( flex: 1, child: Image.asset(width: 75, height: 75, imageTrailingPath)) ], ), subtitle: Text(subtitle, style: TextStyle(fontSize: 12)), children: <Widget>[ const Divider( thickness: 1.0, height: 1.0, ), Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 8.0, ), child: info, ), ), ButtonBar( alignment: MainAxisAlignment.spaceBetween, buttonHeight: 52.0, buttonMinWidth: 90.0, children: <Widget>[ TextButton( style: flatButtonStyle, onPressed: () async { await Clipboard.setData(ClipboardData(text: infoText)); }, child: const Column( children: <Widget>[ Icon(Icons.copy), Padding( padding: EdgeInsets.symmetric(vertical: 2.0), ), Text('Copy'), ], ), ), ], ), ], ), ); } Container _buildStepper() { final canCancel = currentStep > 0; final canContinue = currentStep < 3; StepState? stateStepperUploaded; bool? isActiveUploaded; StepState? stateStepperExtracted; bool? isActiveExtracted; StepState? stateStepperCompared; bool? isActiveCompared; StepState? stateStepperCommitted; bool? isActiveCommitted; StepState? stateStepperEnterQuestion; bool? isActiveEnterQuestion; String? messageStepperEnterQuestion = ""; StepState? stateStepperGenerateSQL; bool? isActiveGenerateSQL; String? messageStepperGenerateSQL = ""; StepState? stateStepperRunQuery; bool? isActiveRunQuery; String? messageStepperRunQuery = ""; StepState? stateStepperGetGraphDescription; bool? isActiveGetGraphDescription; String? messageStepperGraphDescription = ""; StepState? stateStepperGetTextSummary; bool? isActiveGetTextSummary; String? messageStepperGetTextSummary = ""; String? message = ""; void setStepsStates(UpdateStepperState state) { print('Main : _buildStepper() : setStepsStates() : Start'); print( 'Main : _buildStepper() : setStepsStates() : status = ${state.status}'); print('Main : _buildStepper() : currentStep : $currentStep'); switch (state.status) { case StepperStatus.initial: print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.initial'); currentStep = 0; stateStepperEnterQuestion = StepState.disabled; isActiveEnterQuestion = false; stateStepperGenerateSQL = StepState.disabled; isActiveGenerateSQL = false; stateStepperRunQuery = StepState.disabled; isActiveRunQuery = false; stateStepperGetGraphDescription = StepState.disabled; isActiveGetGraphDescription = false; stateStepperGetTextSummary = StepState.disabled; isActiveGetTextSummary = false; message = state.message; break; case StepperStatus.enter_question: stepperExpertInfoList.clear(); currentStep = 0; print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.enter_question'); stateStepperEnterQuestion = StepState.complete; isActiveEnterQuestion = true; stateStepperGenerateSQL = StepState.disabled; isActiveGenerateSQL = false; stateStepperRunQuery = StepState.disabled; isActiveRunQuery = false; stateStepperGetGraphDescription = StepState.disabled; isActiveGetGraphDescription = false; stateStepperGetTextSummary = StepState.disabled; isActiveGetTextSummary = false; messageStepperEnterQuestion = "Question entered in 0 s"; overallProcessingTime = 0; stepperExpertInfoList.add(StepperExpertInfo()); print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.enter_question : stepperExpertInfoList.length = ${stepperExpertInfoList.length}'); print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.enter_question : stepperExpertInfoList = ${stepperExpertInfoList}'); break; case StepperStatus.generate_sql: currentStep = 1; print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.generate_sql'); stateStepperEnterQuestion = StepState.complete; isActiveEnterQuestion = true; stateStepperGenerateSQL = StepState.complete; isActiveGenerateSQL = true; stateStepperRunQuery = StepState.disabled; isActiveRunQuery = false; stateStepperGetGraphDescription = StepState.disabled; isActiveGetGraphDescription = false; stateStepperGetTextSummary = StepState.disabled; isActiveGetTextSummary = false; messageStepperGenerateSQL = state.message! + " " + ((state.debugInfo.stepDuration!.toDouble()) / 1000).toString() + " s"; overallProcessingTime = overallProcessingTime + (state.debugInfo.stepDuration!.toDouble()) / 1000; stepperExpertInfoList.add(state.debugInfo); print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.generate_sql : stepperExpertInfoList.length = ${stepperExpertInfoList.length}'); print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.generate_sql : stepperExpertInfoList = ${stepperExpertInfoList}'); break; case StepperStatus.run_query: currentStep = 2; print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.run_query'); stateStepperEnterQuestion = StepState.complete; isActiveEnterQuestion = true; stateStepperGenerateSQL = StepState.complete; isActiveGenerateSQL = true; stateStepperRunQuery = StepState.complete; isActiveRunQuery = true; stateStepperGetGraphDescription = StepState.disabled; isActiveGetGraphDescription = false; stateStepperGetTextSummary = StepState.disabled; isActiveGetTextSummary = false; messageStepperRunQuery = state.message! + " " + ((state.debugInfo.stepDuration!.toDouble()) / 1000).toString() + " s"; overallProcessingTime = overallProcessingTime + (state.debugInfo.stepDuration!.toDouble()) / 1000; stepperExpertInfoList.add(state.debugInfo); print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.run_query : stepperExpertInfoList.length = ${stepperExpertInfoList.length}'); print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.run_query : stepperExpertInfoList = ${stepperExpertInfoList}'); break; case StepperStatus.get_graph_description: currentStep = 3; print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.get_graph_description'); stateStepperEnterQuestion = StepState.complete; isActiveEnterQuestion = true; stateStepperGenerateSQL = StepState.complete; isActiveGenerateSQL = true; stateStepperRunQuery = StepState.complete; isActiveRunQuery = true; stateStepperGetGraphDescription = StepState.complete; isActiveGetGraphDescription = true; stateStepperGetTextSummary = StepState.disabled; isActiveGetTextSummary = false; messageStepperGraphDescription = state.message! + " " + ((state.debugInfo.stepDuration!.toDouble()) / 1000).toString() + " s"; overallProcessingTime = overallProcessingTime + (state.debugInfo.stepDuration!.toDouble()) / 1000; stepperExpertInfoList.add(state.debugInfo); print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.get_graph_description : stepperExpertInfoList.length = ${stepperExpertInfoList.length}'); print( 'Main : _buildStepper() : setStepsStates() : Switch: StepperStatus.get_graph_description : stepperExpertInfoList = ${stepperExpertInfoList}'); break; default: currentStep = 0; stateStepperUploaded = StepState.disabled; isActiveUploaded = false; stateStepperExtracted = StepState.disabled; isActiveExtracted = false; stateStepperCompared = StepState.disabled; isActiveCompared = false; stateStepperCommitted = StepState.disabled; isActiveCommitted = false; message = state.message; } } Future<void> _dialogRequestGenerated( BuildContext context, StepperExpertInfo debugInfo, String title) { print( "Main: _dialogRequestGenerated() : title = $title : debugInfo.response = ${debugInfo.response}"); return showDialog<void>( context: context, builder: (BuildContext context) { return AlertDialog( title: Text(title), content: SingleChildScrollView( child: Container( width: screenSize!.width / 2, child: Column( children: [ _createDebugInfoCard( Icon(Icons.link), 'URI POST', 'URI used in this step.', 'assets/images/https.png', Text( debugInfo.uri!, style: Theme.of(context) .textTheme .bodyMedium! .copyWith(fontSize: 16), ), debugInfo.uri!), _createDebugInfoCard( //FaIcon(icon), Icon(Icons.https), 'HTTPS Body', 'Json body of the request', 'assets/images/json.png', Container( child: JsonViewer(jsonDecode(debugInfo.body!))), debugInfo.body!), _createDebugInfoCard( //FaIcon(icon), Icon(Icons.https), 'HTTPS Headers', 'Headers sent in the HTTPS request.', 'assets/images/https_header.png', Container( child: JsonViewer(jsonDecode(debugInfo.header!))), debugInfo.header!), _createDebugInfoCard( FaIcon(FontAwesomeIcons.reply), 'HTTPS Response', 'HTTPS body of the response', 'assets/images/json.png', (!debugInfo.response!.contains('syntax error') && !debugInfo.response!.contains('SyntaxError') ) ? Container( child: JsonViewer(jsonDecode(debugInfo.response!..replaceAll('"', '\\"')))) : Container(child: Text(debugInfo.response!)), debugInfo.response!), _createDebugInfoCard( FaIcon(FontAwesomeIcons.code), 'Status Code', 'HTTPS answer status code', 'assets/images/status_code.png', Text( debugInfo.statusCode!.toString(), style: Theme.of(context) .textTheme .bodyMedium! .copyWith(fontSize: 16), ), debugInfo.statusCode!.toString()), _createDebugInfoCard( Icon(Icons.timer), 'Processing Time', 'Step duration in seconds', 'assets/images/elapsed_time.png', Text( (debugInfo.stepDuration!.toDouble() / 1000) .toString() + " s", style: Theme.of(context) .textTheme .bodyMedium! .copyWith(fontSize: 16), ), (debugInfo.stepDuration!.toDouble() / 1000) .toString() + " s"), //Text("SQL request\n: ${debugInfo.generatedSQLText}"), ], )), ), //Text(DicInfoExtractedMap.toString(),), actions: <Widget>[ TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: const Text('Ok'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } return Container( child: BlocBuilder<UpdateStepperCubit, UpdateStepperState>( builder: (context, state) { setStepsStates(state); return Stepper( controlsBuilder: (BuildContext context, ControlsDetails controls) { return Container(); //to remove the continue and cancel buttons }, type: StepperType.horizontal, currentStep: currentStep, onStepTapped: (int index) { String title = ""; print( 'HomePage() : build() : Stepper : onStepTapped() : index = $index'); switch (index) { case 0: title = ""; break; case 1: title = "SQL Request Generation"; break; case 2: title = "Data Retrieval"; break; case 3: title = "Graph Description Retrieval"; break; default: title = ""; break; } print( 'HomePage() : build() : END : Stepper : onStepTapped() : index = $index'); print( 'HomePage() : build() : END : Stepper : onStepTapped() : TextToDocParameter.isTextTodocGlobal = ${TextToDocParameter.isTextTodocGlobal}'); print( 'HomePage() : build() : END : Stepper : onStepTapped() : stepperExpertInfoList.length = ${stepperExpertInfoList.length}'); //temporary knob to dispaly info about TextToDoc request/answer if (TextToDocParameter.isTextTodocGlobal && index == 3 && stepperExpertInfoList.length == 2) { print( 'HomePage() : build() : END : Stepper : onStepTapped() : if(TextToDocParameter.isTextTodocGlobal && index == 4 && stepperExpertInfoList.length == 2 ) = $index'); stepperExpertInfoList.insert(1, StepperExpertInfo()); stepperExpertInfoList.insert(1, StepperExpertInfo()); } if (TextToDocParameter.isTextTodocGlobal && index < 3) { return; } //end of temporary knob _dialogRequestGenerated( context, stepperExpertInfoList[index], title); }, onStepContinue: () {}, onStepCancel: () {}, steps: [ Step( title: Text("Question Typed", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18.0)), subtitle: Text(messageStepperEnterQuestion!), state: stateStepperEnterQuestion!, //stateStepperUploaded!, isActive: isActiveEnterQuestion!, //isActiveUploaded!, content: LimitedBox( maxWidth: screenSize!.width, //100, maxHeight: 40, child: Container( color: Colors.black12, //CupertinoColors.systemGrey, child: Center( child: Padding( padding: const EdgeInsets.only(right: 8, left: 8), child: Text(message!, style: TextStyle( fontWeight: FontWeight.normal, fontSize: 18.0, color: stateStepperEnterQuestion! == StepState.complete ? Colors.green : Colors.red)), ))), ), ), Step( title: Text("SQL Request Generated", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18.0)), subtitle: Text(messageStepperGenerateSQL!), state: stateStepperGenerateSQL!, //stateStepperExtracted!, isActive: isActiveGenerateSQL!, //isActiveExtracted!, content: LimitedBox( maxWidth: screenSize!.width, //100, maxHeight: 40, child: Container( color: Colors.black12, child: Center( child: Padding( padding: const EdgeInsets.only(right: 8, left: 8), child: Text(message!, style: TextStyle( fontWeight: FontWeight.normal, fontSize: 18.0, color: Colors.green)), ), )), ), ), Step( title: Text("Data Retrieved", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18.0)), subtitle: Text(messageStepperRunQuery!), state: stateStepperRunQuery!, //StepState.disabled, //stateStepperCompared!, isActive: isActiveRunQuery!, //isActiveCompared!, content: LimitedBox( maxWidth: screenSize!.width, //100, maxHeight: 40, child: Container( color: Colors.black12, child: Center( child: Padding( padding: const EdgeInsets.only(right: 8, left: 8), child: Text(message!, style: TextStyle( fontWeight: FontWeight.normal, fontSize: 18.0, color: Colors.green)), ))), ), ), Step( title: Text("Graph & Table Generated", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18.0)), subtitle: Text(messageStepperGraphDescription!), state: stateStepperGetGraphDescription!, //StepState.disabled, //stateStepperCompared!, isActive: isActiveGetGraphDescription!, //isActiveCompared!, content: LimitedBox( maxWidth: screenSize!.width, //100, maxHeight: 40, child: Container( color: Colors.black12, child: Center( child: Padding( padding: const EdgeInsets.only(right: 8, left: 8), child: Text( "The overall time to generate the natural language answer is ${double.parse(overallProcessingTime.toStringAsFixed(2))} s.", style: TextStyle( fontWeight: FontWeight.normal, fontSize: 18.0, color: Colors.green)), ))), ), ), ], ); }, ), ); } Future<ui.Image> loadImageAsset(String assetPath) async { final ByteData data = await rootBundle.load(assetPath); final ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List()); final ui.FrameInfo frameInfo = await codec.getNextFrame(); return frameInfo.image; } Future<List<int>> convertImageToListInt(String assetPath) async { ui.Image image = await loadImageAsset(assetPath); ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); return byteData!.buffer.asUint8List(); } Future<Uint8List> _loadFile(String uri) async { final byteData = await rootBundle.load(uri); final buffer = byteData.buffer; Uint8List bytes = buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes); return bytes; } Future<List<List<dynamic>>> importQuestions() async { print('Main: ttmd : importQuestions() : START'); List<List<dynamic>>? rowsAsListOfValues; final filePickerResult = await FilePicker.platform.pickFiles( allowMultiple: false, allowedExtensions: ['csv'], type: FileType.custom, dialogTitle: "Import questions", ); if (filePickerResult != null) { print( 'Main: ttmd : importQuestions() : fileName = ${filePickerResult.files.single.name}'); print( 'Main: ttmd : importQuestions() : size = ${filePickerResult.files.single.size}'); //print('Main: ttmd : importQuestions() : path = ${filePickerResult.files.single.path}'); Uint8List fileBytes = filePickerResult.files.single.bytes!; String fileContent = utf8.decode(fileBytes); List<String> lines = fileContent.split('\n'); print('Main: ttmd : importQuestions() : lines.length = ${lines.length}'); rowsAsListOfValues = const CsvToListConverter( fieldDelimiter: ',', textDelimiter: '"', textEndDelimiter: '"') .convert(fileContent); print( 'Main: ttmd : importQuestions() : rowsAsListOfValues = ${rowsAsListOfValues}'); if (rowsAsListOfValues!.length < 2 || rowsAsListOfValues[0].length > 4 || (rowsAsListOfValues[0].length <= 4 && (rowsAsListOfValues[0][0] != "user_grouping" || rowsAsListOfValues[0][1] != "scenario" || rowsAsListOfValues[0][2] != "question"))) { print( "Main: importQuestions() : WRONG FORMAT : rowsAsListOfValues[0] = ${rowsAsListOfValues[0]}"); checkImportedCSVFile(); return [[]]; } for (var entry in rowsAsListOfValues) { if (rowsAsListOfValues[1].length == 4) { print( "Main: importQuestions() : rowsAsListOfValues = ${entry[0]}, ${entry[2]}, ${entry[3]}"); if (rowsAsListOfValues[1].length != 4) checkImportedCSVFile(); } if (rowsAsListOfValues[1].length == 3) { print( "Main: importQuestions() : rowsAsListOfValues = ${entry[0]}, ${entry[2]}"); if (rowsAsListOfValues[1].length != 3) checkImportedCSVFile(); } } } return rowsAsListOfValues!; } void checkImportedCSVFile() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('Alert'), content: Text( "The imported CVS file does not have the right format or any entry.\n" + "Please follow the format below (CVS comma separated):\n" + "grouping,\tscenario,\tquestion\n" + "grouping1,\tscenario1,\tquestion1\n\n" + "For more info, please look at:\n" + "https://github.com/GoogleCloudPlatform/Open_Data_QnA/blob/main/scripts/known_good_sql.csv", softWrap: true, overflow: TextOverflow.visible), //SwitchExample(), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); // Close the dialog }, child: Text('OK'), ), ], ); }, ); } void noCfgStoredinFirestore() { showDialog( context: context, //navigatorKey.currentContext!, builder: (BuildContext context) { return AlertDialog( title: Row( // Use a Row to align the icon and title children: [ Icon(Icons.warning, color: Colors.red), SizedBox(width: 8), // Add some spacing Text('Alert'), ], ), content: SelectableText.rich( TextSpan( children: [ TextSpan( text: "You must first create and load a configuration file\n" + "using the Settings -> Upload frontend config file option.\n" + 'For that, copy and paste the content below in a file\n' + "that you'll name config_frontend.json:\n\n"), TextSpan( text: "{\n", style: TextStyle(color: Colors.black), ), TextSpan( text: '"endpoint_opendataqnq": ', style: TextStyle(color: Colors.blueAccent), ), TextSpan( text: '"<URI of the backend endpoint>",\n', style: TextStyle(color: Colors.green), ), TextSpan( text: '"firestore_database_id": ', style: TextStyle(color: Colors.blueAccent), ), TextSpan( text: '"opendataqna-session-logs",\n', style: TextStyle(color: Colors.green), ), TextSpan( text: '"firestore_history_collection": ', style: TextStyle(color: Colors.blueAccent), ), TextSpan( text: '"session_logs",\n', style: TextStyle(color: Colors.green), ), TextSpan( text: '"firestore_cfg_collection": ', style: TextStyle(color: Colors.blueAccent), ), TextSpan( text: '"front_end_flutter_cfg",\n', style: TextStyle(color: Colors.green), ), TextSpan( text: '"expert_mode": ', style: TextStyle(color: Colors.blueAccent), ), TextSpan( text: '<true|false>,\n', style: TextStyle(color: Colors.red), ), TextSpan( text: '"anonymized_data": ', style: TextStyle(color: Colors.blueAccent), ), TextSpan( text: '<true|false>,\n', style: TextStyle(color: Colors.red), ), TextSpan( text: '"firebase_app_name": ', style: TextStyle(color: Colors.blueAccent), ), TextSpan( text: '"opendataqna"\n', style: TextStyle(color: Colors.green), ), TextSpan( text: '"imported_questions": ', style: TextStyle(color: Colors.blueAccent), ), TextSpan( text: '"imported_questions"\n', style: TextStyle(color: Colors.green), ), TextSpan( text: '}', style: TextStyle(color: Colors.black), ), ], ), ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); // Close the dialog }, child: Text('OK'), ), ], ); }, ); } List<TreeNode<String>> createQuestionList(List<List<dynamic>> questionList) { List<TreeNode<String>> finalNodeList = []; List<TreeNode<String>>? nodeList = []; List<TreeNode<String>>? nodeEmbeddedList; String scenario_nameCurrent = ""; int count = 1; int countQuestions = 0; bool isNewScenario = true; String nodeTmp = ""; print("Main: createQuestionList() : START"); print( "Main: createQuestionList() : questionList.length = ${questionList.length}"); print( "Main: createQuestionList() : questionList[0].length = ${questionList[0].length}"); for (var entry in questionList) print( "Main: createQuestionList() : questionList = ${entry[1]}, ${entry[2]}"); scenario_nameCurrent = (questionList[0][1] as String).trim().toLowerCase(); nodeTmp = ""; for (int i = 0; i < questionList.length; i++) { print( "Main: createQuestionList() : START LOOP : i = $i : scenario_nameCurrent = $scenario_nameCurrent"); if (i < questionList.length - 1) { print( "Main: createQuestionList() : START LOOP : i = $i : $i < ${questionList.length - 1}"); //We're on the same scenario if (scenario_nameCurrent == (questionList[i][1] as String).trim().toLowerCase()) { print( "Main: createQuestionList() : LOOP : i = $i : scenario_nameCurrent = questionList[i][1] as String).trim().toLowerCase() = $scenario_nameCurrent"); //processQuestionRow(i); if (isNewScenario) { print( "Main: createQuestionList() : LOOP : i = $i : This is a new sceanrio : isNewScenario = $isNewScenario"); nodeList = []; nodeEmbeddedList = []; finalNodeList.add(TreeNode<String>( 'Scenario $count - ${questionList[i][1]} - ${getQuestionCount(questionList, scenario_nameCurrent)}', subNodes: nodeList!)); isNewScenario = false; } nodeTmp = "$count:" + questionList[i][2] + ":" + questionList[i][1] + ":" + questionList[i][0]; print( "Main: createQuestionList() : LOOP : i = $i : scenario_nameCurrent = $scenario_nameCurrent : nodeTmp = $nodeTmp"); if ((questionList[i][3] as String).trim().toLowerCase() == "y" && (questionList[i + 1][3] as String).trim().toLowerCase() == "y" && (questionList[i + 1][1] as String).trim().toLowerCase() == scenario_nameCurrent) { print( "Main: createQuestionList() : LOOP : i = $i : scenario_nameCurrent = $scenario_nameCurrent : main_question$i = ${(questionList[i][3] as String).trim().toLowerCase()} : main_question${i + 1} = ${(questionList[i + 1][3] as String).trim().toLowerCase()}"); nodeList! .add(TreeNode<String>(nodeTmp, subNodes: nodeEmbeddedList!)); } else if ((questionList[i][3] as String).trim().toLowerCase() == "y" && (questionList[i + 1][1] as String).trim().toLowerCase() != scenario_nameCurrent) { print( "Main: createQuestionList() : LOOP : i = $i : scenario_nameCurrent = $scenario_nameCurrent : main_question$i = ${(questionList[i][3] as String).trim().toLowerCase()} : main_question${i + 1} = ${(questionList[i + 1][3] as String).trim().toLowerCase()} : questionList[i+1][1] = ${questionList[i + 1][1]} != scenario_nameCurrent = $scenario_nameCurrent"); nodeList!.add(TreeNode<String>(nodeTmp)); } else if ((questionList[i][3] as String).trim().toLowerCase() == "y" && (questionList[i + 1][3] as String).trim().toLowerCase() == "n") { //this is a new main question nodeEmbeddedList = []; print( "Main: createQuestionList() : LOOP : i = $i : scenario_nameCurrent = $scenario_nameCurrent : main_question$i = ${(questionList[i][3] as String).trim().toLowerCase()} != main_question${i + 1} = ${(questionList[i + 1][3] as String).trim().toLowerCase()}"); /*nodeEmbeddedList!.add(TreeNode<String>("$count:" + questionList[i][2] + ":" + questionList[i][1] + ":" + questionList[i][0])); nodeList!.add(TreeNode<String>(nodeTmp, subNodes: nodeEmbeddedList!));*/ nodeList! .add(TreeNode<String>(nodeTmp, subNodes: nodeEmbeddedList!)); } else if ((questionList[i][3] as String).trim().toLowerCase() == "n" && (questionList[i + 1][3] as String).trim().toLowerCase() == "n") { //this is a follow-up question print( "Main: createQuestionList() : LOOP : i = $i : scenario_nameCurrent = $scenario_nameCurrent : main_question$i = ${(questionList[i][3] as String).trim().toLowerCase()} == main_question${i + 1} = ${(questionList[i + 1][3] as String).trim().toLowerCase()}"); nodeEmbeddedList!.add(TreeNode<String>("$count:" + questionList[i][2] + ":" + questionList[i][1] + ":" + questionList[i][0])); } else if ((questionList[i][3] as String).trim().toLowerCase() == "n" && (questionList[i + 1][3] as String).trim().toLowerCase() == "y") { //this is a new main question print( "Main: createQuestionList() : LOOP : i = $i : scenario_nameCurrent = $scenario_nameCurrent : main_question$i = ${(questionList[i][3] as String).trim().toLowerCase()} == main_question${i + 1} = ${(questionList[i + 1][3] as String).trim().toLowerCase()}"); nodeEmbeddedList!.add(TreeNode<String>("$count:" + questionList[i][2] + ":" + questionList[i][1] + ":" + questionList[i][0])); } } else { print( "Main: createQuestionList() : i = $i : $scenario_nameCurrent != ${questionList[i][1]} : countQuestions = $countQuestions"); isNewScenario = true; count++; //processQuestionRow(i); nodeList = []; nodeEmbeddedList = []; finalNodeList.add(TreeNode<String>( 'Scenario $count - ${questionList[i][1]} - ${getQuestionCount(questionList, questionList[i][1])}', subNodes: nodeList!)); isNewScenario = false; nodeTmp = "$count:" + questionList[i][2] + ":" + questionList[i][1] + ":" + questionList[i][0]; print( "Main: createQuestionList() : i = $i : $scenario_nameCurrent != ${questionList[i][1]} : nodeTmp = $nodeTmp"); nodeList!.add(TreeNode<String>(nodeTmp, subNodes: nodeEmbeddedList!)); scenario_nameCurrent = (questionList[i][1] as String).trim().toLowerCase(); print( "Main: createQuestionList() : i = $i : new scenario_nameCurrent = $scenario_nameCurrent "); } } //end of if i < questionList.length - 1 else if (i == questionList.length - 1) { print( "Main: createQuestionList() : LOOP : Last row : i = $i : i = ${questionList.length - 1}"); if ((questionList[i][3] as String).trim().toLowerCase() == "y") { print( "Main: createQuestionList() : LOOP : Last row : i = $i : scenario_nameCurrent = $scenario_nameCurrent : main_question$i = ${(questionList[i][3] as String).trim().toLowerCase()}"); //nodeList!.add(TreeNode<String>(nodeTmp, subNodes: nodeEmbeddedList!)); nodeList!.add(TreeNode<String>("$count:" + questionList[i][2] + ":" + questionList[i][1] + ":" + questionList[i][0])); } else { //this is a new main question //nodeEmbeddedList = []; print( "Main: createQuestionList() : LOOP : Last row : i = $i : scenario_nameCurrent = $scenario_nameCurrent : main_question$i = ${(questionList[i][3] as String).trim().toLowerCase()}"); nodeEmbeddedList!.add(TreeNode<String>("$count:" + questionList[i][2] + ":" + questionList[i][1] + ":" + questionList[i][0])); } } } //end of for print("Main: createQuestionList() : END : count = $count"); return finalNodeList; } int getQuestionCount(List<List<dynamic>> questionList, String scenario) { int scenarioCount = 0; bool hit = false; print("Main: getQuestionCount() : START "); for (int i = 0; i < questionList.length; i++) { if (scenario.toLowerCase() == (questionList[i][1] as String).trim().toLowerCase()) { hit = true; scenarioCount++; } else { if (hit) break; } } print("Main: getQuestionCount() : scenarioCount = $scenarioCount"); return scenarioCount; } Future<List<Map<String, dynamic>>> _getLastQuestions() async { List<Map<String, dynamic>> resp = []; print("Main : _getLastQuestions() : START"); print( 'Main : _getLastQuestions() : TextToDocParameter.userID = ${TextToDocParameter.userID}'); print('Main : _getLastQuestions() : db = ${db}'); print('Main : _getLastQuestions() : db.app = ${db.app}'); print('Main : _getLastQuestions() : db.databaseId = ${db.databaseId}'); var querySnapshot = await db .collection("session_logs") .where("user_id", isEqualTo: TextToDocParameter.userID) .limit(4) .orderBy('timestamp', descending: true) .get(); print( "Main : _getLastQuestions() : querySnapshot.docs.length = ${querySnapshot.docs.length}"); print("Main : _getLastQuestions() : querySnapshot = ${querySnapshot}"); for (var docSnapshot in querySnapshot.docs) { print( 'Main : _getLastQuestions() : ${docSnapshot.id} => ${docSnapshot.data()}'); resp.add({ "user_question": "${docSnapshot.data()['user_question']}", "timestamp": "${DateTime.fromMillisecondsSinceEpoch(docSnapshot.data()['timestamp'].seconds * 1000)}", "user_grouping": "${docSnapshot.data().containsKey('user_grouping') ? docSnapshot.data()['user_grouping'] : 'no data'}", "scenario_name": "${docSnapshot.data().containsKey('scenario_name') ? docSnapshot.data()['scenario_name'] : 'no data'}" }); } return resp; } Future<List<String>> _getLastQuestionsOld(String userGrouping) async { List<String> respLLMQuestion = []; List<String> respCannedQuestions = []; List<String> resp = []; List<String> tmpQuestions = []; String body = ""; Uri url; String question1 = ""; String question2 = ""; String question3 = ""; String question4 = ""; String timeString = ""; String originalQuestion = ""; print( 'NewSuggestionCubit : getLastQuestions() : generateNewSuggestions : START'); print( 'NewSuggestionCubit : getLastQuestions() : userGrouping = $userGrouping'); timeString = displayDateTime(); //Create the header Map<String, String>? _headers = { "Content-Type": "application/json", //"Authorization": " Bearer ${client!.credentials.accessToken.toString()}", }; //Create the body body = '''{ "user_grouping": "$userGrouping" }'''; try { var response = await html.HttpRequest.requestCrossOrigin( '${TextToDocParameter.endpoint_opendataqnq}/get_known_sql', method: "POST", sendData: body); print('NewSuggestionCubit : getLastQuestions() : response = ' + response.toString()); final jsonData = jsonDecode(response); if (jsonData != null) { print('NewSuggestionCubit: getLastQuestions() : jsonData = $jsonData'); //KnownSQL = [{"example_user_question": "question1", "example_generated_sql": "sql1"}, // {"example_user_question": "question2", "example_generated_sql": "sql2"}, // ...] var knownSql = jsonData["KnownSQL"].replaceAll(RegExp(r'((\\n)|(\\r))'), ''); var knownSqlMap = jsonDecode(knownSql); print( 'NewSuggestionCubit: getLastQuestions() : knownSqlMap = ${knownSqlMap}'); print( 'NewSuggestionCubit: getLastQuestions() : knownSqlMap[0] = ${knownSqlMap[0].toString()}'); for (int i = 0; i < knownSqlMap.length; i++) { for (var entry in knownSqlMap[i].entries) { print('${entry.key} : ${entry.value}'); if (entry.key == "example_user_question") tmpQuestions.add(entry.value); } } print('Main: getLastQuestions() : tmpQuestions = ${tmpQuestions}'); question1 = tmpQuestions[0] ?? "No suggestion for question1"; question2 = tmpQuestions[1] ?? "No suggestion for question2"; question3 = tmpQuestions[2] ?? "No suggestion for question3"; question4 = tmpQuestions[3] ?? "No suggestion for question4"; //Adding scenarioNumber + ":" to have same format as for Business KPI questions question1 = "2:" + question1; question2 = "2:" + question2; question3 = "2:" + question3; question4 = "2:" + question4; print('Main: getLastQuestions() : question1 = ${question1}'); print('Main: getLastQuestions() : question2 = ${question2}'); print('Main: getLastQuestions() : question3 = ${question3}'); print('Main: getLastQuestions() : question4 = ${question4}'); } } catch (e) { print( 'Main: getLastQuestions() : Not a canned question : EXCEPTION = $e'); throw Exception('Failed to get earning calls question suggestions: $e'); } finally { resp.add(question1); resp.add(question2); resp.add(question3); resp.add(question4); print('Main: getLastQuestions() : resp = $resp'); return resp; } } void SaveImportedQuestionsToFirestore( List<List<dynamic>> questionList) async { int count = 0; print('Main: SaveImportedQuestionsToFirestore() : START'); print( 'Main: SaveImportedQuestionsToFirestore() : questionList = $questionList'); if (questionList.length > 0) { try { //Delete all former questions for that user var querySnapshot = await db! .collection("${TextToDocParameter.imported_questions}") .where("user_id", isEqualTo: TextToDocParameter.userID) .get(); for (var docSnapshot in querySnapshot.docs) { db.collection("imported_questions").doc('${docSnapshot.id}').delete(); } //create new questions to be stored on Firestore for (int i = 0; i < questionList.length; i++) { List list = questionList[i]; print('Main: SaveImportedQuestionsToFirestore() : List = $List'); Map<String, dynamic> questionMap = {}; questionMap['user_grouping'] = list[0]; questionMap['scenario'] = list[1]; questionMap['question'] = list[2]; questionMap['user_id'] = TextToDocParameter.userID; if (list.length == 4) questionMap['main_question'] = list[3]; else questionMap['main_question'] = "Y"; questionMap['order'] = count++; db .collection("imported_questions") .doc("question$i") .set(questionMap); } } catch (e) { print('Main: SaveImportedQuestionsToFirestore() : EXCEPTION : e = $e'); } } } } class Config { static bool isUseFeedback = false; static bool isUseColorMode = false; static bool isUseDashboards = false; static bool isUseReports = false; static bool isExpert = false; static bool isUseLog = false; static bool isAnonymizedMode = false; }