Future copyGraphToClipBoard()

in frontend/frontend-flutter/lib/screens/bot.dart [1181:1927]


  Future<void> copyGraphToClipBoard(String imageKey, String summaryText) async {
    print('Bot : copyGraphToClipBoard() : START');
    print('Bot : copyGraphToClipBoard() : imageKey = $imageKey');
    print('Bot : copyGraphToClipBoard() : summaryText = $summaryText');

    //For now, the copy button only copies text. Copy of images/widgets is supported, so I'm commenting out the copy of image until
    //the flutter_inappwebview package used to render Google Charts is implementing InAppWebViewController.takeScreenshot() on Flutter Web.
    if(false) {
    //if (imageKey != "no_image") {
      print('Bot : copyGraphToClipBoard() : imageKey != "no_image"');
      print('Bot : copyGraphToClipBoard() : _graphsImagesMap.length = ${_graphsImagesMap.length}');
      Uint8List imgBytes = _graphsImagesMap[imageKey!]!;

      if (imgBytes != null) {
        print('Bot : copyGraphToClipBoard() : imgBytes != null');
        final base64Image = base64Encode(imgBytes);

        try {
          js.context.callMethod('copyBase64ImageToClipboard', [base64Image]);

        } catch (e) {
          print('Bot : copyGraphToClipBoard() : EXCEPTION :  e = $e');
        }
      }
    } else if (summaryText != null || summaryText.length > 0) {
      print('Bot : copyGraphToClipBoard() : summaryText != null || summaryText.length > 0');
      await Clipboard.setData(ClipboardData(text: summaryText));
    } else {
      print('Bot : copyGraphToClipBoard() : else');
      await Clipboard.setData(ClipboardData(text: "No data available."));
    }
  }

  Future<List<Object>?> getChatResponseNew(String msg, String mime, String id,
      {types.User? user}) async {
    List<String>? reqResp = [];
    List<Object> RespList = [];
    Uri urlGenerateSQL;
    Uri urlRunQuery;
    String generatedSQLText = "";
    String userQuestion = "";
    int statusCodeRunQuery = 0;
    String jsonResponseRunQuery = "";

    print('Bot : getChatResponseNew() : START ');

    urlGenerateSQL =
        Uri.parse('${TextToDocParameter.endpoint_opendataqnq}/generate_sql');

    print('Bot : getChatResponseNew() : urlGenerateSQL = ' +
        urlGenerateSQL.host +
        urlGenerateSQL.path);

    print('Bot : getChatResponseNew() : mime = ' + mime);

    Map<String, String>? _headers = {
      "Content-Type": "$mime",
    };

    print(
        'Bot : getChatResponseNew() : BEFORE prepending main question : msg = ' +
            msg);
    if (mainQuestionsFollowUpQuestions.containsKey(msg)) {
      msg = mainQuestionsFollowUpQuestions[msg];
    }

    print(
        'Bot : getChatResponseNew() : AFTER prepending main question : msg = ' +
            msg);

    userQuestion = msg;
    print('Bot : getChatResponseNew() : userQuestion = ' + userQuestion);

    String _body1 = '''{
    "session_id" :"${TextToDocParameter.sessionId}",
    "user_id":"${TextToDocParameter.userID}",
    "user_question":"${userQuestion}",
    "user_grouping":"${TextToDocParameter.currentUserGrouping}"
    }''';

    print('Bot : getChatResponseNew() : _body1 = ' + _body1);
    final stopwatchGenerateSQL = Stopwatch()..start();

    final _response =
        await http.post(urlGenerateSQL, headers: _headers, body: _body1);

    stopwatchGenerateSQL.stop();

    responsePalMBody = _response.body.replaceAll(
        RegExp(r'(\\u003cb|\\u003e|\\u003c|\\u003e|(\/n)|(\\r))'), '');

    print('Bot : getChatResponseNew() : responsePalMBody = $responsePalMBody');

    var error = pickFromJson(responsePalMBody!, 'Error').asStringOrNull();

    print('Bot : getChatResponseNew() : error = $error');
    print(
        'Bot : getChatResponseNew() : _response.statusCode = ${_response.statusCode}');

    if (_response.statusCode == 200 &&
        responsePalMBody!.toLowerCase().contains("select") &&
        (error!.length == 0 ?? false)) {
      print(
          'Bot : getChatResponseNew() : _response.statusCode == 200 && (error!.length == 0 ?? false )');

      TextToDocParameter.sessionId =
          pickFromJson(responsePalMBody!, 'SessionID').asStringOrNull()!;

      print(
          'Bot : getChatResponseNew() : _response.statusCode == 200 && (error!.length == 0 ?? false ) : TextToDocParameter.sessionId = ${TextToDocParameter.sessionId}');

      reqResp.add(_body1);
      reqResp.add(responsePalMBody);

      print('Bot : getChatResponseNew() : /generate_sql : reqResp[0] = ' +
          reqResp[0]);
      print('Bot : getChatResponseNew() : /generate_sql : reqResp[1] = ' +
          reqResp[1]);

      //get the generated SQL query
      generatedSQLText = extractContentGenerateSQL(responsePalMBody);

      //Update stepper state to generate_sql
      BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded(
          status: StepperStatus.generate_sql,
          message: "SQL request generated.",
          stateStepper: StepState.complete,
          isActiveStepper: true,
          debugInfo: StepperExpertInfo(
              uri: urlGenerateSQL.host + urlGenerateSQL.path,
              body: _body1,
              header: jsonEncode(_headers),
              response: responsePalMBody,
              generatedSQLText: generatedSQLText,
              statusCode: _response.statusCode,
              stepDuration: stopwatchGenerateSQL.elapsed.inMilliseconds,
              answerList: [generatedSQLText]));
      print(
          "main: getChatResponseNew() : After BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded() : generate_sql");
      print(
          "main: getChatResponseNew() : generate_sql : generatedSQLText = $generatedSQLText");

      urlRunQuery =
          Uri.parse('${TextToDocParameter.endpoint_opendataqnq}/run_query');

      String _body2 = '''{
      "user_question": "${userQuestion}",
      "user_grouping": "${TextToDocParameter.currentUserGrouping}",
    "generated_sql": "${generatedSQLText}",
    "session_id" : "${TextToDocParameter.sessionId}"
    }''';

      //send the request to get the results in tabular format
      var stopwatchRunQuery = Stopwatch()..start();
      final _responseTabResults =
          await http.post(urlRunQuery, headers: _headers, body: _body2);
      stopwatchRunQuery.stop();

      statusCodeRunQuery = _responseTabResults.statusCode;
      jsonResponseRunQuery = _responseTabResults.body;

      print('Bot : getChatResponseNew() : tabular results: urlRunQuery = ' +
          urlRunQuery.toString());

      print(
          'Bot : getChatResponseNew() : tabular results : _body2 = ' + _body2);
      print(
          'Bot : getChatResponseNew() : tabular results: _responseTabResults.body = ' +
              jsonResponseRunQuery);

      if (TextToDocParameter.anonymized_data) {
        print(
            'Bot : getChatResponseNew() : tabular results: TextToDocParameter.anonymized_data = ${TextToDocParameter.anonymized_data}');
        jsonResponseRunQuery = anonymizedData(jsonResponseRunQuery!);
      }

      //return extractContent(_responseTabResults.body, id, user: user!);
      return extractContentResultsOpenDataQnA(
          jsonResponseRunQuery: jsonResponseRunQuery,
          userQuestion: userQuestion,
          generatedSQLText: generatedSQLText,
          urlRunQuery: urlRunQuery,
          bodyRunQuery: _body2,
          headersRunQuery: _headers,
          statusCodeRunQuery: statusCodeRunQuery,
          elapsedTimeRunQuery: stopwatchRunQuery.elapsed.inMilliseconds);
    } else {
      //Update stepper state to generate_sql
      BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded(
          status: StepperStatus.generate_sql,
          message: "SQL request generated in",
          stateStepper: StepState.complete,
          isActiveStepper: true,
          debugInfo: StepperExpertInfo(
              uri: urlGenerateSQL.host + urlGenerateSQL.path,
              body: _body1,
              header: jsonEncode(_headers),
              response: responsePalMBody,
              generatedSQLText:
                  "An error occurred, no SQL request has been generated.",
              statusCode: _response.statusCode,
              stepDuration: stopwatchGenerateSQL.elapsed.inMilliseconds,
              answerList: [
                "An error occurred, no SQL request has been generated."
              ]));
      print(
          'Bot : getChatResponseNew() : tabular results : _response.statusCode = ${_response.statusCode} and error attribute is set');
      RespList.add("");
      RespList.add(true);
      RespList.add("");
      RespList.add(
          "The request did not return meaningful information. It could be because the question has not been formulated properly or some context is missing.");

      return RespList;
    }
  }

  String extractContentGenerateSQL(String jsonResponse) {
    print("bot() : extractContentGenerateSQL() : START");
    print("bot() : extractContentGenerateSQL() : jsonResponse = $jsonResponse");

    String generatedSQLText = "";
    var error = pickFromJson(jsonResponse, 'Error');
    var generatedSQLTmp =
        pickFromJson(jsonResponse, 'GeneratedSQL').asStringOrNull();
    var responseCode = pickFromJson(jsonResponse, 'ResponseCode');

    print(
        "bot() : extractContentGenerateSQL() : generatedSQLTmp = ${generatedSQLTmp}");

    generatedSQLText = generatedSQLTmp!
        .replaceAll('\n', ' ')
        .replaceAll('"', '\\"'); //generates an exception if null => fix it

    print(
        "bot() : extractContentGenerateSQL() : generatedSQLText = ${generatedSQLText}; ");

    return generatedSQLText!;
  }

  Future<List<Object>> extractContentResultsOpenDataQnA(
      {String? jsonResponseRunQuery,
      String? userQuestion,
      String? generatedSQLText,
      Uri? urlRunQuery,
      String? bodyRunQuery,
      Map<String, String>? headersRunQuery,
      int? statusCodeRunQuery,
      int? elapsedTimeRunQuery}) async {
    //String generatedSQLText = "";
    List<Object> RespList = [];
    bool isText = true;
    dynamic googleChartVizRes;
    String? naturalResponseText = "";

    String textSummary =
        "The request did not return meaningful information. It could be because the question has not been formulated properly or some context is missing.";
    String? knownDB = "";
    String? error = "";

    error = pickFromJson(jsonResponseRunQuery!, 'Error').asStringOrNull();
    knownDB = pickFromJson(jsonResponseRunQuery!, 'KnownDB').asStringOrNull();
    var responseCode = pickFromJson(jsonResponseRunQuery!, 'ResponseCode');
    naturalResponseText =
        pickFromJson(jsonResponseRunQuery!, 'NaturalResponse').asStringOrNull();

    print(
        "bot() : extractContentResultsOpenDataQnA() : knownDB.length = ${knownDB!.length}; ");
    print(
        "bot() : extractContentResultsOpenDataQnA() : knownDB = ${knownDB}; ");

    print(
        "bot() : extractContentResultsOpenDataQnA() : bodyRunQuery = ${bodyRunQuery}; ");

    //Update stepper state to run_query
    BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded(
        status: StepperStatus.run_query,
        message: "Tabular data retrieved in",
        stateStepper: StepState.complete,
        isActiveStepper: true,
        debugInfo: StepperExpertInfo(
            uri: urlRunQuery!.host + urlRunQuery!.path,
            body: bodyRunQuery,
            header: jsonEncode(headersRunQuery!),
            response: jsonResponseRunQuery
                .replaceAll("\"[", "[")
                .replaceAll("]\"", "]")
                .replaceAll("\\\"", "\""),
            knownDB: knownDB,
            statusCode: statusCodeRunQuery,
            stepDuration: elapsedTimeRunQuery,
            answerList: [knownDB]));
    print(
        "main: extractContentResultsOpenDataQnA() : After BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded() : run_query");

    try {
      if (knownDB != "[]" && error!.length == 0 ?? false) {
        print("bot() : extractContentResults() : VALID ANSWER");
        var knowDBJson = jsonDecode(knownDB!);
        //Check if it is worth displaying data. If just one row is returned, no use.
        //if (true) {
        if ((knowDBJson!.length <= 1 && (knowDBJson![0] as Map).length <= 1) ||
            (knowDBJson![0] as Map).length > 2) {
          print(
              "bot() : extractContentResultsOpenDataQnA() : knowDBJson!.length <= 1 and MAP has at most 1 element");
          isText = true;
          //Get graph description in case we want to display a graph
          googleChartVizRes = await getDataVisualization(
              userQuestion!, knownDB!, generatedSQLText!);

          print(
              "bot() : extractContentResultsOpenDataQnA() : googleChartVizRes = ${googleChartVizRes}");
        } else {
          print(
              "bot() : extractContentResultsOpenDataQnA() : knowDBJson!.length > 1 or MAP has at least 1 element");
          isText = false;
          //Get graph description in case we want to display a graph
          googleChartVizRes = await getDataVisualization(
              userQuestion!, knownDB!, generatedSQLText!);

          print(
              "bot() : extractContentResultsOpenDataQnA() : googleChartVizRes = ${googleChartVizRes}");
        }

        print(
            "bot() : extractContentResultsOpenDataQnA() : isText = ${isText}; ");
        //get ML summarize
        //textSummary = await getTextSummary(userQuestion!, knownDB!);

        RespList.add(knownDB!);
        RespList.add(isText);
        RespList.add(googleChartVizRes!);
        RespList.add(naturalResponseText!.trim());
      } else {
        print("bot() : extractContentResultsOpenDataQnA() : UNVALID ANSWER");
        RespList.add("");
        RespList.add(true);
        RespList.add({"chart_div": "empty", "chart_div_1": "empty"});
        RespList.add(
            "The request did not return meaningful information. It could be because the question has not been formulated properly or some context is missing.");
      }
    } catch (e) {
      print("bot() : extractContentResultsOpenDataQnA() : EXCEPTION : $e");
    } finally {
      print("bot() : extractContentResultsOpenDataQnA() : FINALLY CLAUSE ");
      RespList.add(knownDB!);
      RespList.add(isText);
      RespList.add(googleChartVizRes!);
      RespList.add(naturalResponseText!);
      return RespList;
    }
  }

  Future<dynamic> getDataVisualization(
      String question, String tabularAnswer, String generatedSQLText) async {
    dynamic generatedChartjsMap;

    //Create the header
    Map<String, String>? _headers = {
      "Content-Type": "application/json",
    };

    String tmpReplace = tabularAnswer.replaceAll('"', '\\"');
    print('Bot : getDataVisualization() :  tnpReplace = ' + tmpReplace);

    //Create the body
    String _body = '''{
        "user_question": "$question",
        "sql_generated": "${generatedSQLText}",
        "sql_results": "${tmpReplace}",
        "session_id" : "${TextToDocParameter.sessionId}"
      }''';

    print('Bot : getDataVisualization() :  _body = ' + _body);

    try {
      var stopwatchGetDataVisulization = Stopwatch()..start();

      print('Bot: getDataVisualization() : BEFORE HttpRequest');
      var response = await html.HttpRequest.requestCrossOrigin(
          '${TextToDocParameter.endpoint_opendataqnq}/generate_viz',
          method: "POST",
          sendData: _body);
      print('Bot: getDataVisualization() : AFTER HttpRequest');
      stopwatchGetDataVisulization.stop();

      print('Bot : getDataVisualization() : response = ' + response.toString());

      var jsonData = jsonDecode(response);

      if (jsonData != null) {
        print('Bot: getDataVisualization() : jsonData = $jsonData');

        generatedChartjsMap = jsonData["GeneratedChartjs"];

        print(
            'Bot: getDataVisualization() : generatedChartjsMap = $generatedChartjsMap');

        if(TextToDocParameter.anonymized_data) {
          //update generatedChartjsMap
          mapAnonymisationGraph.forEach((key, value) {
            print(
                'Bot : getDataVisualization() : update jsonData : key =  $key, value = $value');
            generatedChartjsMap['chart_div'] =
                generatedChartjsMap['chart_div'].replaceAll(key, value);
          });
        }

        //Update stepper state to get_graph_description
        BlocProvider.of<UpdateStepperCubit>(context)
            .updateStepperStatusUploaded(
                status: StepperStatus.get_graph_description,
                message: "Graph generated",
                stateStepper: StepState.complete,
                isActiveStepper: true,
                debugInfo: StepperExpertInfo(
                  uri:
                      "${TextToDocParameter.endpoint_opendataqnq}/generate_viz",
                  body: _body
                      .replaceAll("\"[", "[")
                      .replaceAll("]\"", "]")
                      .replaceAll("\\\"", "\""),
                  header:
                      '''{"Header not accessible": "CORS headers are not accessible as they are sent directly by the web browser"}''',
                  response: response
                      .replaceAll("\"[", "[")
                      .replaceAll("]\"", "]")
                      .replaceAll("\\\"", "\""),
                  statusCode: 0,
                  stepDuration:
                      stopwatchGetDataVisulization.elapsed.inMilliseconds,
                ));

        print(
            "Bot: getDataVisualization() : After BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded() : get Google Charts");
      } else {
        print('Bot: getDataVisualization() : jsonData is null');
      }
    } catch (e) {
      generatedChartjsMap = {"chart_div": "empty", "chart_div_1": "empty"};
      print('Bot: getDataVisualization() : EXCEPTION : error = $e');
    } finally {
      return generatedChartjsMap!;
    }
  }

  List<String> setBubbleColor(String text, {types.User? user}) {
    String tmp = "";
    String userType = "";
    const String datastoreGenerated = "ap :";
    const String nMatchLLMGenerated = "nh :";
    int startIndex = 0;
    List<String> rep = [];

    print('Bot : setBubbleColor() : text = ' + text);

    if (user != null) if (_user.id == user!.id) userType = "user";

    if (text.contains(datastoreGenerated)) {
      startIndex =
          text.indexOf(datastoreGenerated) + datastoreGenerated.length + 1;
      colorBubble = "datastoreData";
    } else if (text.contains(nMatchLLMGenerated)) {
      startIndex =
          text.indexOf(nMatchLLMGenerated) + datastoreGenerated.length + 1;
      colorBubble = "noMatchLLM";
    } else if (userType == "user") {
      colorBubble = "user";
    } else {
      colorBubble = "regularDF";
    }

    tmp = text.substring(startIndex);

    print('Bot : setBubbleColor() : startIndex = ' + startIndex.toString());
    print('Bot : setBubbleColor() : tmp = ' + tmp);
    print('Bot : setBubbleColor() : colorBubble = ' + colorBubble);

    rep.add(tmp);
    rep.add(colorBubble);
    return rep;
  }

  int countOccurences(String mainString, String search) {
    int lInx = 0;
    int count = 0;
    while (lInx != -1) {
      lInx = mainString.indexOf(search, lInx);
      if (lInx != -1) {
        count++;
        lInx += search.length;
      }
    }
    return count;
  }

  Future<void> _dialogExtension(BuildContext context, String extension) {
    return showDialog<void>(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text("Format d'image non supporté",
              style: TextStyle(
                  fontWeight: FontWeight.normal,
                  fontSize: 22.0,
                  color: Colors.blue)),
          content: Text(
            "Le format d'image $extension n'est pas supporté.\n" +
                "Choisissez une image du type :\ngif, tiff, tif, jpg, jpeg, png, bmp, webp",
          ),
          actions: <Widget>[
            TextButton(
              style: TextButton.styleFrom(
                textStyle: Theme.of(context).textTheme.labelLarge,
              ),
              child: const Text('Ok'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  String displayDateTime() {
    String? dateTimeS;

    final now = DateTime.now();
    dateTimeS = DateFormat('yyyy-MM-dd HH:mm:ss').format(now);
    return dateTimeS!;
  }

  List<types.Message> get messages {
    return _messages;
  }

  Map<String, Uint8List> get graphsImages {
    return _graphsImagesMap;
  }

  Map<String, PaginatedDataTable> get tableKeysMap {
    return tableKeyMap;
  }

  Widget avatarBuilder(types.User user) {
    bool isUserAvatar = user.id == '82091010-a484-4a89-ae75-a22bf8d6f3ab';

    return CircleAvatar(
      backgroundColor: Colors.green,
      backgroundImage:
          isUserAvatar ? NetworkImage(TextToDocParameter.picture) : null,
      radius: 16,
      child: !isUserAvatar
          ? Text(
              "TA",
            )
          : null,
    );
  }

  String anonymizedData(String responseRunQuery) {
    String anonymizedData = "";
    Random random = new Random();
    mapAnonymisationGraph.clear();

    print("Bot : anonymizedData() : START");
    print("Bot : anonymizedData() : responseRunQuery = ${responseRunQuery}");

    var responseRunQueryJson = jsonDecode(responseRunQuery);
    print(
        "Bot : anonymizedData() : responseRunQueryJson = ${responseRunQueryJson}");

    var knownDB = jsonDecode(responseRunQueryJson["KnownDB"]);
    String naturalResponse = responseRunQueryJson["NaturalResponse"];

    print("Bot : anonymizedData() : knownDB = ${knownDB}");
    print("Bot : anonymizedData() : naturalResponse = ${naturalResponse}");

    for (int i = 0; i < knownDB.length; i++) {
      var entry = knownDB[i];
      print("Bot : anonymizedData() : for :  i = $i : entry = ${entry}");

      entry.forEach((key, value) {
        print('Bot : anonymizedData() : key = $key : value =  $value');

        try {
          if (value is double) {
            print(
                'Bot : anonymizedData() : i = $i : value =  $value is of type double');
            double randomNumber = random.nextDouble() * value;
            int truncatedInt = (randomNumber * 100).toInt();
            randomNumber = truncatedInt / 100;
            print(
                'Bot : anonymizedData() : i = $i : randomNumber =  $randomNumber');
            entry[key] = randomNumber;

            NumberFormat formatter = NumberFormat("#,##0", "en_US"); // Adjust locale if needed
            String formattedNumber = formatter.format(value);

            mapAnonymisationGraph[formattedNumber] = formatter.format(randomNumber);//randomNumber.toString();
          }
          if (value is String) {
            print(
                'Bot : anonymizedData() : i = $i : value =  $value is of type String');

            String randomString = generateRandomString(value.toString().length);
            print(
                'Bot : anonymizedData() : i = $i : randomString =  $randomString');
            entry[key] = randomString;

            mapAnonymisationGraph[value.toString()] = randomString;
          }
        } catch (e) {
          print('Bot : anonymizedData() : PARSING EXCEPTION =  $e');
        }
      });
    }
    responseRunQueryJson["KnownDB"] = jsonEncode(knownDB);

    //update NaturalResponse
    mapAnonymisationGraph.forEach((key, value) {
      print(
          'Bot : anonymizedData() : update NaturalResponse : key =  $key, value = $value');
      naturalResponse = naturalResponse.replaceAll(key,value);
    });

    print("Bot : anonymizedData() : END : knownDB = ${knownDB}");
    print("Bot : anonymizedData() : END : naturalResponse = ${naturalResponse}");

    responseRunQueryJson["NaturalResponse"] = naturalResponse;

    print(
        "Bot : anonymizedData() : END : responseRunQueryJson = ${responseRunQueryJson}");
    return jsonEncode(responseRunQueryJson);
  }


  String generateRandomString(int length) {
    const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
    Random _rnd = Random();

    return String.fromCharCodes(Iterable.generate(
    length, (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length))));
  }


  Future<void> _updateUserGroupingInSessionLogs() async {
    int count = 0;
    print(
        "Bot: _updateUserGroupingInSessionLogs() : START");

    //get all the documents corresponding to the user_id of the current user
    try {
      var querySnapshot = await widget.db!
          .collection("${TextToDocParameter.firestore_history_collection}")
          .where("user_id", isEqualTo: TextToDocParameter.userID)
          .orderBy('timestamp', descending: true)
          .limit(1)
          .get();

      print(
          "Bot: _updateUserGroupingInSessionLogs() : Add user_grouping : querySnapshot.docs.length = ${querySnapshot.docs.length}");
      print(
          "Bot: _updateUserGroupingInSessionLogs() : Add user_grouping : querySnapshot = ${querySnapshot}");

      //update all these documents with the user_grouping scenario_name
      if(TextToDocParameter.currentScenarioName.isEmpty)
        TextToDocParameter.currentScenarioName = "Scenario";

      for (var docSnapshot in querySnapshot.docs) {
        print(
            'Bot: _updateUserGroupingInSessionLogs() : Add user_grouping : TextToDocParameter.currentUserGrouping = ${TextToDocParameter.currentUserGrouping}');
        print(
            'Bot: _updateUserGroupingInSessionLogs() : Add user_grouping : TextToDocParameter.currentScenarioName = ${TextToDocParameter.currentScenarioName}');

        print(
            'Bot: _updateUserGroupingInSessionLogs() : Add user_grouping : ${docSnapshot.id} => ${docSnapshot.data()}');
        widget.db!.collection("${TextToDocParameter.firestore_history_collection}").doc('${docSnapshot.id}').set(
            {"user_grouping": "${TextToDocParameter.currentUserGrouping}", "scenario_name": "${TextToDocParameter.currentScenarioName}"},
            SetOptions(merge: true));
        count++;
      }
    } catch (e) {
      print(
          'Bot: _updateUserGroupingInSessionLogs() : Add user_grouping : EXCEPTION : $e');
    }
    var snackBar = SnackBar(
      content:
      Text('Updated $count questions on ${TextToDocParameter.firestore_database_id} collection'),
      duration: Duration(seconds: 3),
    );
    ScaffoldMessenger.of(context).showSnackBar(snackBar);
  }

  PaginatedDataTable? createPaginatedTable(String data) {
    print("bot() : createPaginatedTable() : START");
    print("bot() : createPaginatedTable() : data = $data");
    List<DataColumn> dataColumnList = <DataColumn>[];

    List<dynamic> dataList = jsonDecode(data!);

    print("bot() : createPaginatedTable() : dataList = $dataList");
    print("bot() : createPaginatedTable() : dataList.length = ${dataList.length}");

    //get headers of columns
    var entry = dataList.first;
    print("bot() : createPaginatedTable() : entry = ${entry}");
    print("bot() : createPaginatedTable() : entry.length = ${entry.length}");
    print("bot() : createPaginatedTable() : entry['tconst'] = ${entry['tconst']}");
    print("bot() : createPaginatedTable() : entry['original_title'] = ${entry['original_title']}");
    print("bot() : createPaginatedTable() : entry['average_rating'] = ${entry['average_rating']}");
    print("bot() : createPaginatedTable() : entry['title_type'] = ${entry['title_type']}");

    for (var element in (entry as Map<String, dynamic>).entries) {
      print('bot() : createPaginatedTable() : Key: ${element.key}, Value: ${element.value}');
      dataColumnList.add(DataColumn(label: Text(element.key.toString(), style: TextStyle(fontWeight: FontWeight.bold, color : Colors.white))));
    }

    if (dataList.length != 0) {
      print("bot() : createPaginatedTable() : dataList.length != 0}");

        var rowsList = dataList.map((data) {
          List<DataCell> dataCellsList = <DataCell>[];

          for (var element in (data as Map<String, dynamic>).entries) {
            print('bot() : createPaginatedTable() : Key: ${element.key}, Value: ${element.value}');
            DataCell cell = DataCell(Text(element.value.toString()));
            dataCellsList.add(cell);
          }
          return DataRow(cells: dataCellsList);
        }).toList();

        return PaginatedDataTable(
          //header: Text('Results'),
          headingRowColor: WidgetStateProperty.all(Colors.blue),
          rowsPerPage: 3, // Customize as needed
          columns: dataColumnList,
          source: OpenDataQnASource(rowsList as List<DataRow>),
        );
    }
    else
      return null;
  }

}