frontend/frontend-flutter/lib/screens/bot.dart (1,632 lines of code) (raw):
import 'dart:typed_data';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:intl/intl.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'dart:convert';
import 'dart:math';
import 'package:file_picker/file_picker.dart';
import 'package:bubble/bubble.dart';
import 'package:http/http.dart' as http;
import 'package:deep_pick/deep_pick.dart';
import 'package:ttmd/utils/stepper_expert_info.dart';
import '../services/first_question/first_question_cubit.dart';
import '../services/load_question/load_question_cubit.dart';
import '../services/new_suggestions/new_suggestion_cubit.dart';
import '../services/update_stepper/update_stepper_cubit.dart';
import '../services/update_stepper/update_stepper_state.dart';
import '../utils/TextToDocParameter.dart';
import '../utils/custom_input_field.dart' as cif;
import '../utils/custom_input_field.dart';
import 'dart:html' as html;
import 'package:screenshot/screenshot.dart';
import '../utils/tabbed_container.dart';
import 'package:simple_http_api/simple_http_api.dart';
import "dart:js" as js;
// For the testing purposes, you should probably use https://pub.dev/packages/uuid.
String randomString() {
final random = Random.secure();
final values = List<int>.generate(16, (i) => random.nextInt(255));
return base64UrlEncode(values);
}
class Bot extends StatefulWidget {
final TextEditingController? textEditingController;
final FirebaseFirestore? db;
const Bot({Key? key, this.textEditingController, this.db}) : super(key: key);
@override
State<Bot> createState() => BotState();
}
class BotState extends State<Bot> with SingleTickerProviderStateMixin {
final List<types.Message> _messages = [];
final Map<String, Uint8List> _graphsImagesMap = {};
final _user = types.User(
id: '82091008-a484-4a89-ae75-a22bf8d6f3ac',
firstName: '${TextToDocParameter.firstName}',
lastName: '${TextToDocParameter.lastName}'
);
final _userAvatar = types.User(
id: '82091010-a484-4a89-ae75-a22bf8d6f3ab',
firstName: '${TextToDocParameter.firstName}',
lastName: '${TextToDocParameter.lastName}'
);
final _user1 = const types.User(
id: '82091009-a485-4a90-ae76-a22bf8d6f3ad',
firstName: 'Open Data QnA',
lastName: 'Assistant');
String textMLKit = "";
String textDocAi = "";
String responsePalMBody = "";
String streamingText = "";
String requestPalMBody = "";
String responseDLPMBody = "";
String requestDLPBody = "";
List<String> sourceList = [];
Map<String, List<String>> mapSource = Map();
String colorBubble = "user";
Chat? chat;
Size? screenSize;
bool isFirstQuestion = true;
bool isProcessing = false;
ScreenshotController screenshotController = ScreenshotController();
ScreenshotController screenshotController1 = ScreenshotController();
List<GlobalKey<PaginatedDataTableState>> tableKeyList = [];
Map<String, PaginatedDataTable> tableKeyMap = {};
String? imageId;
bool isGraphKeyAdded = false;
bool isTableKeyAdded = false;
Map mainQuestionsFollowUpQuestions = {};
late TabController _tabController;
bool _isThumbsUpHovered = false;
bool _isThumbsDownHovered = false;
bool _isCopyHovered = false;
static bool isTextToDoc = false;
int _selectedIndex = 0;
InAppWebViewController? webViewController;
Map<String,String> mapAnonymisationGraph = {};
Container? graphContainer;
@override
void initState() {
setup();
super.initState();
}
@override
void dispose() {
_tabController!.dispose();
webViewController!.dispose();
super.dispose();
}
Future<void> setup() async {
_tabController = TabController(length: 2, vsync: this);
}
@override
Widget build(BuildContext context) {
screenSize = MediaQuery.of(context).size;
chat = Chat(
emptyState: Text(""),
avatarBuilder: avatarBuilder,
customBottomWidget: CustomInputField(
onAttachmentPressed: _handleAttachmentPressed,
onSendPressed: _handleSendPressed,
db: widget.db,
options: cif.InputOptions(
textEditingController: widget.textEditingController)),
customMessageBuilder: customMessageBuilder,
//inputOptions: InputOptions(textEditingController: widget.textEditingController),
messages: _messages,
onSendPressed: _handleSendPressed,
//onAttachmentPressed: _handleImageSelection,
//onAttachmentPressed: _handleFileSelection
onAttachmentPressed: _handleAttachmentPressed,
onMessageTap: _handleMessageTap,
onPreviewDataFetched: _handlePreviewDataFetched,
showUserAvatars: true,
showUserNames: true,
bubbleBuilder: _bubbleBuilder,
user: _user,
messageWidthRatio: 0.9,
theme: DefaultChatTheme(
seenIcon: Text(
'read',
style: TextStyle(
fontSize: 10.0,
),
),
backgroundColor: Color(
0xFFF0F2F6), //Color.fromRGBO(242, 242, 242, 1.0),//Colors.black12,
messageMaxWidth: screenSize!.width,
),
);
print(
" bot: build() : TextToDocParameter.isTextTodocGlobal = ${TextToDocParameter.isTextTodocGlobal}");
return Flexible(
fit: FlexFit.loose,
flex: 9,
child: Stack(children: <Widget>[
Screenshot(child: chat!, controller: screenshotController1),
isProcessing
? Positioned(
left: 650,
top: 300,
child: SizedBox(
width: 100,
height: 100,
child: CircularProgressIndicator(
strokeWidth: 6,
),
),
)
: dummyFunction(),
]),
);
}
Text dummyFunction() {
print("Bot: dummyFunction() : START");
Future.delayed(const Duration(seconds: 2), () {
generateSnapshot();
});
return Text("");
}
Future<void> generateSnapshot() async {
print("Bot: generateSnapshot() : START");
//Does not work for now because the flutter_inappwebview is not supported by the creenshot package
//it return a blank image. InAppWebViewController.takeScreenshot() is not implemented for the web platform.
//So commenting out the code below :
/*if (isGraphKeyAdded == true &&
!TextToDocParameter.isTextTodocGlobal) {
print(
"Bot: generateSnapshot() : isGraphKeyAdded = $isGraphKeyAdded");
print("Bot: generateSnapshot() : imageId = ${imageId}");
Uint8List img = await _generateImage(graphContainer!);
_graphsImagesMap[imageId!] = img;
isGraphKeyAdded = false;
print("Bot: generateSnapshot() : isGraphKeyAdded = $isGraphKeyAdded");
print("Bot: generateSnapshot() : _graphsImagesMap.length = ${_graphsImagesMap.length}");
} */
if (tableKeyList.isNotEmpty &&
isTableKeyAdded == true &&
!TextToDocParameter.isTextTodocGlobal) {
print(
"Bot: generateSnapshot() : tableKeyList.length = ${tableKeyList.length}");
print(
"Bot: generateSnapshot() : tableKeyList.isNotEmpty = ${tableKeyList.isNotEmpty} && isTableKeyAdded = $isTableKeyAdded");
print(
"Bot: generateSnapshot() : tableKeyList.last = ${tableKeyList.last.toString()}");
/* Commenting out this code until syncfusion_flutter_pdfviewer package is replaced
PaginatedDataTable pdfGrid = await tableKeyList.last.currentState!.widget;
print("Bot: generateSnapshot() : pdfGrid = $pdfGrid");
tableKeyMap[tableKeyList.last.toString()] = pdfGrid!;
print(
"Bot: generateSnapshot() : tableKeyMap.length = ${tableKeyMap.length}");
print("Bot: generateSnapshot() : isTableKeyAdded = $isTableKeyAdded"); */
isTableKeyAdded = false;
}
//tableKeyMap
}
void _addMessage(types.Message message) {
print('Bot : _addMessage() message.text = ' + message.toJson().toString());
setState(() {
_messages.insert(0, message);
});
print('Bot : _addMessage() : _messages.length = ' +
_messages.length.toString());
print('Bot : _addMessage() : _messages = ' + _messages.toString());
}
void _handleSendPressed(types.PartialText message) {
final textMessage = types.TextMessage(
author: _userAvatar,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
text: message.text,
type: types.MessageType.text,
metadata: {"dataSource": "user"});
bool isACannedQuestion = false;
print('Bot : _handleSendPressed() textMessage.text = ' + textMessage.text);
print('Bot : _handleSendPressed() textMessage.author.firstName = ' +
textMessage.author.firstName!);
_addMessage(textMessage);
//Remove the welcome message after the first question asked
if (isFirstQuestion) {
print('Bot : _handleSendPressed() : isFirstQuestion = ' +
isFirstQuestion.toString());
isFirstQuestion = false;
BlocProvider.of<FirstQuestionCubit>(context).removeWelcomeMessage();
}
print(
"main: initState() : After BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded() : TextToDocParameter.lastScenarioNumber = ${TextToDocParameter.lastScenarioNumber}");
BlocProvider.of<NewSuggestionCubit>(context).generateNewSuggestions(
TextToDocParameter.lastScenarioNumber, message.text,
lastCannedQuestion: TextToDocParameter.lastCannedQuestion,
isACannedQuestion: isACannedQuestion);
//Update the question history on the side menu
//BlocProvider.of<LoadQuestionCubit>(context).loadQuestionToChat(question: message.text, time: displayDateTime());
BlocProvider.of<LoadQuestionCubit>(context)
.loadQuestionToChat(question: message.text, time: "rr");
//Update stepper state
BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded(
status: StepperStatus.enter_question,
message: "Question entered.",
stateStepper: StepState.complete,
isActiveStepper: true);
print(
"main: initState() : After BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded() : stepper initialized");
_handleReceivedResponse(textMessage.text, "text");
setState(() {
isProcessing = true;
});
}
void _handleReceivedResponse(String msg, String type) async {
String mime = "";
String id = randomString();
bool isText = true;
dynamic dataViz;
GlobalKey<PaginatedDataTableState> tableKey = GlobalKey();
imageId = randomString();
PaginatedDataTable? tableGrid;
//TextToDocParameter.isTextTodocGlobal = true;
print('Bot : _handleReceivedResponse(): START ');
print('Bot : _handleReceivedResponse(): isTextToDoc = ' +
isTextToDoc.toString());
print(
'Bot : _handleReceivedResponse(): TextToDocParameter.isTextTodocGlobal = ${TextToDocParameter.isTextTodocGlobal}');
switch (type) {
case "text":
mime = "application/json";
break;
case "pdf":
mime = "application/pdf";
break;
case "png":
//case "image":
mime = "image/png";
break;
case "jpeg":
//case "image":
mime = "image/jpeg";
break;
default:
print('Error, out of range : index = $type ');
}
print('Bot : _handleReceivedResponse(): type = ' +
type +
' : mime = ' +
mime);
print('Bot : _handleReceivedResponse(): msg = ' + msg);
if (!TextToDocParameter.isTextTodocGlobal) {
//NL2SQL request
print('Bot : _handleReceivedResponse(): USING NL2SQL');
//Get generated reponse
var rep = await getChatResponseNew(msg, mime, id, user: _user1);
print(
'Bot : _handleReceivedResponse(): back from getChatResponseNew() : rep = $rep');
//graphConfig = rep![2] as GraphConfig;
dataViz = rep![2];
if ((rep![0] as String).length == 0) {
//knownDB
rep[0] =
'[{"response": "The request did not return meaningful information. It could be because the question has not been formulated properly or some context is missing."}]';
} else {
//The request has been successful and an entry has been created on ${TextToDocParameter.firestore_database_id}
//Adding the user_grouping and scenario_name to the entry because as of now it does not contain this data.
//If in the future user_grouping is added, _updateUserGroupingInSessionLogs() below can be removed
_updateUserGroupingInSessionLogs();
tableGrid = createPaginatedTable(rep[0] as String);
tableKeyList.add(tableKey);
isTableKeyAdded = true;
print(
'Bot : _handleReceivedResponse(): tableKey = $tableKey : isTableKeyAdded = $isTableKeyAdded');
}
isText = rep[1] as bool;
print(
'Bot : _handleReceivedResponse(): repFinal = ' + (rep[0] as String));
print(
'Bot : _handleReceivedResponse(): isText = ' + (rep[1].toString()));
print(
'Bot : _handleReceivedResponseNew() : CustomMessage : isText = ${isText}');
if (isText) {
print(
'Bot : _handleReceivedResponseNew() : isText = $isText : dataViz = ${dataViz}');
imageId = "no_image";
} else {
print(
'Bot : _handleReceivedResponseNew() : CustomMessage : imageId = $imageId');
}
final customMessage = types.CustomMessage(
author: _user1,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
type: types.MessageType.custom,
metadata: {
"graph": dataViz,
"textSummary": rep[3] as String,
"imageId": imageId,
"dataSource": tableGrid,
"tableKey": tableKey.toString(),
});
_addMessage(customMessage);
} else {
print('Bot : _handleReceivedResponse(): USING TEXT2DOC');
var rep =
await getChatResponseTextToDoCStream(msg, mime, id, user: _user1);
imageId = "no_image";
final customMessage = types.CustomMessage(
author: _user1,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
type: types.MessageType.custom,
metadata: {
"graph": dataViz,
"textSummary": rep![3] as String,
"imageId": imageId,
"dataSource": null,
"tableKey": tableKey.toString(),
"stream": rep![4] as Stream<BaseChunk<Object>> ?? null,
"stopWatch": rep![5] as Stopwatch ?? null
});
_addMessage(customMessage);
}
setState(() {
isProcessing = false;
});
}
Future<List<Object>?> getChatResponseTextToDoCStream(
String msg, String mime, String id,
{types.User? user}) async {
List<String>? reqResp = [];
List<Object> RespList = [];
Uri url;
String userQuestion = "";
String body = "";
print('Bot : getChatResponseTextToDoCStream() : START');
url = Uri.parse(
'https://colab-cloudrun-template-ra1-uz6w7mqrka-uc.a.run.app/generate_streaming');
print('Bot : getChatResponseTextToDoCStream() : url = ' +
url.host +
url.path);
Map<String, String>? headers = {
"Content-Type": "$mime",
};
print('Bot : getChatResponseTextToDoCStream() : headers = $headers');
userQuestion = msg;
print(
'Bot : getChatResponseTextToDoCStream() : userQuestion = $userQuestion');
body = '''{
"query": "${userQuestion}"
}''';
print('Bot : getChatResponseTextToDoCStream() : request_body = $body');
var eventSource = EventSource(url, ApiMethod.post);
eventSource.setHeaders(headers);
final cancelToken = TimingToken(Duration(seconds: 5));
final stopwatchtextToDoc = Stopwatch()..start();
//final stream = eventSource.send(body: body, cancelToken: cancelToken);
var stream = eventSource.send(body: body).asBroadcastStream();
print('Bot : getChatResponseTextToDoCStream() : stream = $stream');
RespList.add("test"); // 0 : body of the answer
RespList.add(true); //1 : is text
RespList.add("dump"); // 2 : Graph config
//RespList.add("Your request can not be answered right now. Please try again.");
RespList.add("firstChunck"); // 3: text
RespList.add(stream); //4: stream
RespList.add(stopwatchtextToDoc); //5: stopwatchtextToDoc
print('Bot : getChatResponseTextToDoCStream() : END');
return RespList;
}
Future<String> waitForFirstSSEData(
Stream<BaseChunk<Object>> stream, EventSource eventSource) async {
int count = 0;
print('Bot : waitForFirstSSEData() : START');
stream.listen(
(event) {
if (eventSource.isWeb) {
print('Bot : waitForFirstSSEData(): eventSource.isWeb');
print('Bot : waitForFirstSSEData(): count = $count');
print('Bot : waitForFirstSSEData(): event.chunk = ${event.chunk}');
responsePalMBody = responsePalMBody + event.chunk.toString();
var answerPlainTextChunck =
pickFromJson(event.chunk.toString(), 'response').asStringOrNull();
print(
'Bot : waitForFirstSSEData(): answerPlainTextChunck = ${answerPlainTextChunck}');
setState(() {
streamingText = streamingText + answerPlainTextChunck!;
});
if (count == 0) {
final textMessage = types.TextMessage(
author: _user1,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
text: streamingText,
type: types.MessageType.text,
metadata: {"dataSource": null});
_addMessage(textMessage);
}
count++;
} else {
print('Bot : waitForFirstSSEData(): eventSource.isNotWeb');
final encoding = event.getEncoding();
print(
'Bot : waitForFirstSSEData(): eventSource.isNotWeb : encoding.decode(event.chunk as List<int>) = ${encoding.decode(event.chunk as List<int>)}');
}
},
onError: (err) => print('Bot : waitForFirstSSEData(): err = $err'),
onDone: () {
eventSource.close;
streamingText = "";
},
);
return "";
}
Future<List<Object>?> getChatResponseTextToDoC(
String msg, String mime, String id,
{types.User? user}) async {
List<String>? reqResp = [];
List<Object> RespList = [];
Uri url;
String userQuestion = "";
String body = "";
String finalAnswer = "";
print('Bot : getChatResponseTextToDoC() : START');
url = Uri.parse(
'https://multi-modal-rag-dgujjntxuq-uc.a.run.app/generate-answer');
print('Bot : getChatResponseTextToDoC() : url = ' + url.host + url.path);
print('Bot : getChatResponseTextToDoC() : mime = ' + mime);
Map<String, String>? _headers = {
"Content-Type": "$mime",
};
userQuestion = msg;
print('Bot : getChatResponseTextToDoC() : userQuestion = ' + userQuestion);
body = '''{
"query": "${userQuestion}"
}''';
print('Bot : getChatResponseTextToDoC() : request_body = ' + body);
final stopwatchtextToDoc = Stopwatch()..start();
final _response = await http.post(url, headers: _headers, body: body);
stopwatchtextToDoc.stop();
if (_response.statusCode == 200) {
print('Bot : getChatResponseTextToDoC() : Status code 200 ');
responsePalMBody = _response.body
.replaceAll(RegExp(r'(\\u003cb|\\u003e|\\u003c|\\u003e|(\/n))'), '');
var answerPlainText =
pickFromJson(responsePalMBody, 'response').asStringOrNull();
if (answerPlainText != null) {
finalAnswer = answerPlainText;
print(
'Bot : getChatResponseTextToDoC() : answerPlainText != null : finalAnswer = ' +
finalAnswer);
} else {
finalAnswer =
"Your request can not be answered right now. Please try again.";
print(
'Bot : getChatResponseTextToDoC() : answerPlainText == null : finalAnswer = ' +
finalAnswer);
}
reqResp.add(body);
reqResp.add(responsePalMBody);
print(
'Bot : getChatResponseTextToDoC() : /generate_answer : reqResp[0] = ' +
reqResp[0]);
print(
'Bot : getChatResponseTextToDoC() : /generate_answer : reqResp[1] = ' +
reqResp[1]);
RespList.add(responsePalMBody); // 0 : body of the answer
RespList.add(true); //1 : is text
RespList.add("dump"); // 2 : Grah config
RespList.add(finalAnswer); //3 : answer in plain text
//Update stepper state to get_text_summary
BlocProvider.of<UpdateStepperCubit>(context).updateStepperStatusUploaded(
status: StepperStatus.get_text_summary,
message: "NL answer received in",
stateStepper: StepState.complete,
isActiveStepper: true,
debugInfo: StepperExpertInfo(
uri: url!.host + url!.path,
body: body,
header: jsonEncode(_headers!),
response: responsePalMBody,
summary: finalAnswer,
statusCode: _response.statusCode,
stepDuration: stopwatchtextToDoc.elapsed.inMilliseconds,
));
} else {
print(
'Bot : getChatResponseTextToDoC() : _response.statusCode = ${_response.statusCode}');
RespList.add("test"); // 0 : body of the answer
RespList.add(true); //1 : is text
RespList.add("dump"); // 2 : Grah config
RespList.add(
"Your request can not be answered right now. Please try again."); //3 : answer in plain text
}
print('Bot : getChatResponseTextToDoC() : END');
return RespList;
}
Future<dynamic> ShowCapturedWidget(
BuildContext context, Uint8List capturedImage) {
print("Bot: ShowCapturedWidget() : START");
return showDialog(
useSafeArea: false,
context: context,
builder: (context) => Scaffold(
appBar: AppBar(
title: Text("Captured widget screenshot"),
),
body: Center(child: Image.memory(capturedImage)),
),
);
}
List<String> transfromDynamicListToStringList(List<dynamic> list) {
List<String> dataList = [];
print(
'Bot: transfromDynamicListToStringList() : list.length = ${list.length}');
print('Bot: transfromDynamicListToStringList() : list = $list');
for (int i = 0; i <= list.length - 1; i++) {
dataList.add(list.elementAt(i) as String);
}
if (dataList is List<String>)
print(
'BarGraph: transfromDynamicListToStringList() : dataList is of type List<String>');
print(
'BarGraph: transfromDynamicListToStringList() : dataList = $dataList');
return dataList;
}
void _handleFileSelection() async {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['pdf'],
);
if (result != null && result.files.single.path != null) {
String res = "";
final message = types.FileMessage(
author: _user,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: randomString(),
name: result.files.single.name,
size: result.files.single.size,
uri: result.files.single.path!,
);
_addMessage(message);
print('Bot : _handleReceivedResponse() uri = ' + message.uri);
textDocAi = "not implemented yet"; //await extractTextMLKit(message.uri);
res = textDocAi.replaceAll('"', " ");
print('Bot : _handleReceivedResponse() : textDocAi = ' + res);
_handleReceivedResponse(
'Ecris en français un résumé du texte ci-dessous en mois de 50 mots:\n' +
res,
"png");
}
}
void _handleMessageTap(BuildContext _, types.Message message) async {
print('Bot : _handleMessageTap() : DEBUT = ');
if (message is types.FileMessage) {
print('Bot : _handleMessageTap() : types.FileMessage : message.uri = ' +
message.uri);
}
}
void _handlePreviewDataFetched(
types.TextMessage message,
types.PreviewData previewData,
) {
final index = _messages.indexWhere((element) => element.id == message.id);
final updatedMessage = (_messages[index] as types.TextMessage).copyWith(
previewData: previewData,
);
setState(() {
_messages[index] = updatedMessage;
});
}
void _handleAttachmentPressed() {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => SafeArea(
child: SizedBox(
height: 144,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Align(
alignment: AlignmentDirectional.centerStart,
child: Text('Photo'),
),
),
TextButton(
onPressed: () {
Navigator.pop(context);
_handleFileSelection();
},
child: const Align(
alignment: AlignmentDirectional.centerStart,
child: Text('File'),
),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Align(
alignment: AlignmentDirectional.centerStart,
child: Text('Cancel'),
),
),
],
),
),
),
);
}
Widget _bubbleBuilder(
Widget child, {
required message,
required nextMessageInGroup,
}) {
String colorString = colorBubble;
return Container(
width: screenSize!.width,
child: Bubble(
child: child,
color: message.author.id !=
'82091010-a484-4a89-ae75-a22bf8d6f3ab'
? const Color(0xfff5f5f7) //Color(0xfff5f5f7)
: const Color(0xffffffff), //Color(0xfffaf9de),
margin: null,
nip: BubbleNip.no,
),
);
}
Widget customMessageBuilder(types.CustomMessage customMessage,
{required int messageWidth}) {
print('Bot : customMessageBuilder(): START');
String inputString = "";
return Container(
//width: 500,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
//Display the firstname and lastname
Container(
padding: const EdgeInsets.only(top: 20, left: 20),
width: screenSize!.width,
child: Text(_user1.firstName! + " " + _user1.lastName! + "\n",
style: TextStyle(
fontSize: 12,
color: Colors.green,
fontWeight: FontWeight.bold),
textAlign: TextAlign.left)),
//Display answer
!customMessage.metadata!.containsKey('stream') ||
customMessage.metadata!['textSummary'] != "firstChunck"
? Container(
padding: const EdgeInsets.only(top: 20, left: 20),
width: screenSize!.width,
child: Text(customMessage.metadata!['textSummary'],
textAlign: TextAlign.start, style: TextStyle(fontSize: 16)),
)
: StreamBuilder<BaseChunk<Object>>(
stream: customMessage.metadata!['stream'],
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
print(
'Bot : customMessageBuilder(): StreamBuilder : snapshot.connectionState : WAITING');
streamingText = "";
return SizedBox(
width: 100,
height: 100,
child: CircularProgressIndicator(
strokeWidth: 6,
),
); // Display a loading indicator when waiting for data.
} else if (snapshot.hasError) {
print(
'Bot : customMessageBuilder(): StreamBuilder : snapshot.hasError : ERROR');
return Text(
'Error: ${snapshot.error}'); // Display an error message if an error occurs.
} else if (!snapshot.hasData) {
print(
'Bot : customMessageBuilder(): StreamBuilder : !snapshot.hasData : No data available');
return Text(
'No data available'); // Display a message when no data is available.
} else {
print(
'Bot : customMessageBuilder(): StreamBuilder : snapshot.hasData');
var answerPlainTextChunck = pickFromJson(
snapshot.data!.chunk.toString(), 'response')
.asStringOrNull();
if (answerPlainTextChunck != "end_stream") {
//streamingText = streamingText + answerPlainTextChunck!;
inputString = inputString + answerPlainTextChunck!;
print(
'Bot : customMessageBuilder(): StreamBuilder : snapshot.hasData : answerPlainTextChunck != end_stream');
//customMessage.metadata!['eventSource'].close();
} else {
customMessage.metadata!['textSummary'] = inputString;
print(
'Bot : customMessageBuilder(): StreamBuilder : snapshot.hasData : answerPlainTextChunck == end_stream');
Stopwatch stopwatchStreaming =
customMessage.metadata!['stopWatch'] as Stopwatch;
stopwatchStreaming.stop();
//Update stepper state to get_text_summary
BlocProvider.of<UpdateStepperCubit>(context)
.updateStepperStatusUploaded(
status: StepperStatus.get_text_summary,
message: "NL answer received in",
stateStepper: StepState.complete,
isActiveStepper: true,
debugInfo: StepperExpertInfo(
uri:
"https://colab-cloudrun-template-ra1-uz6w7mqrka-uc.a.run.app/generate_streaming",
body: '{"N/A": "N/A"}',
header: '{"Content-Type": "app/json}',
response: responsePalMBody,
summary: inputString,
statusCode: 200,
stepDuration: stopwatchStreaming.elapsed
.inMilliseconds, //stopwatchtextToDoc.elapsed.inMilliseconds,
));
}
return Container(
padding: const EdgeInsets.only(top: 20, left: 20),
width: screenSize!.width,
child: Text(inputString, //streamingText,
textAlign: TextAlign.start,
style: TextStyle(fontSize: 16)),
);
}
}),
if (customMessage.metadata!['dataSource'] != null &&
!customMessage.metadata!['textSummary']
.contains("The request did not return meaningful information"))
Container(
child: TabbedContainer(
initialIndex: customMessage.metadata!['graph'] != null ? 1 : 0,
controller: _tabController,
tabs: const [
Tab(text: "Table", icon: Icon(Icons.table_rows_sharp)),
Tab(text: "Graph", icon: Icon(Icons.bar_chart)),
],
tabViews: [
Center(
child: SingleChildScrollView(child: Container(width: screenSize!.width ,child: customMessage.metadata!['dataSource'] ?? Text('No Data')))),
Center(
child: getGoogleGraph(customMessage.metadata!['graph']) ?? Text('No Graph')),
],
),
),
SizedBox(height: 40),
//Add feedback
Container(
width: screenSize!.width / 10,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.black12, // Adjust the radius as needed
),
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
showFeedbackDialog();
},
onHover: (value) {
setState(() {
_isThumbsUpHovered = value;
});
},
child: Tooltip(
message: "Provide your feedback on the generated answer",
child: new Image(
image: new AssetImage("assets/images/thumbs_up1.png"),
width: 20,
height: 20,
color: _isThumbsUpHovered
? Colors.grey.withOpacity(0.5)
: null,
colorBlendMode:
_isThumbsUpHovered ? BlendMode.modulate : null,
fit: BoxFit.scaleDown,
alignment: Alignment.center,
),
),
),
InkWell(
onTap: () {
showFeedbackDialog();
},
onHover: (value) {
setState(() {
_isThumbsDownHovered = value;
});
},
child: Tooltip(
message: "Provide your feedback on the generated answer",
child: new Image(
image: new AssetImage("assets/images/thumbs_down1.png"),
width: 20,
height: 20,
color: _isThumbsDownHovered
? Colors.grey.withOpacity(0.5)
: null,
colorBlendMode:
_isThumbsDownHovered ? BlendMode.modulate : null,
fit: BoxFit.scaleDown,
alignment: Alignment.center,
),
),
),
InkWell(
onTap: () {
print("Bot: copy : onTap : START");
print('Bot : customMessageBuilder() : copy : onTap: () : START');
copyGraphToClipBoard(customMessage.metadata!['imageId'],
customMessage.metadata!['textSummary']);
},
onHover: (value) {
setState(() {
_isCopyHovered = value;
});
},
child: Tooltip(
message: "Copy the answer",
child: new Image(
image: new AssetImage("assets/images/copy1.png"),
width: 20,
height: 20,
color:
_isCopyHovered ? Colors.grey.withOpacity(0.5) : null,
colorBlendMode:
_isCopyHovered ? BlendMode.modulate : null,
fit: BoxFit.scaleDown,
alignment: Alignment.center,
),
),
),
],
),
),
)
//customMessage.metadata!['graph'],
],
));
}
Widget getGoogleGraph(dynamic dataViz) {
print(" bot: getGoogleGraph() : START");
Container container;
if (dataViz!["chart_div"]! != null) {
print(
'bot: getGoogleGraph() : dataViz!["chart_div"]! = ${dataViz!["chart_div"]!}');
String htmlPre = """<html>
<head>
<!--Load the AJAX API-->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript"> """;
String htmlPost = """</script>
</head>
<body>
<!--Div that will hold the pie chart-->
<div id="chart_div"></div>
</body>
</html>""";
container = Container(
child: InAppWebView(
initialData: InAppWebViewInitialData(
data: htmlPre + "\n" + dataViz!["chart_div"]!.replaceAll('width: 600,','width: 1200,').replaceAll('height: 300,', 'height: 600,') + htmlPost + "\n"),
onWebViewCreated: (controller) {
webViewController = controller;
},
),
);
isGraphKeyAdded = true;
graphContainer = container;
return container;
} else {
return Text("No Chart",
style: TextStyle(
fontSize: 16,
color: Colors.indigoAccent,
fontWeight: FontWeight.bold));
}
}
Widget getGoogleTable(dynamic dataViz) {
print("bot: getGoogleTable() : START");
if (dataViz!["chart_div_1"]! != null) {
print(
'bot: getGoogleTable() : dataViz!["chart_div_1"]! = ${dataViz!["chart_div_1"]!}');
String htmlPre = """<html>
<head>
<!--Load the AJAX API-->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript"> """;
String htmlPost = """</script>
</head>
<body>
<!--Div that will hold the pie chart-->
<div id="chart_div_1"></div>
</body>
</html>""";
return Container(
child: InAppWebView(
initialData: InAppWebViewInitialData(
data:
htmlPre + "\n" + dataViz!["chart_div_1"]!.replaceAll('width: 600,','width: 1200,').replaceAll('height: 300,', 'height: 600,') + htmlPost + "\n"),
onWebViewCreated: (controller) {
webViewController = controller;
},
),
);
} else
return Text("No data",
style: TextStyle(
fontSize: 16,
color: Colors.indigoAccent,
fontWeight: FontWeight.bold));
}
Future<Uint8List> _generateImage(Widget widget) async {
print('Bot : _generateImage() : START');
//Uint8List capturedImage = await screenshotController.captureFromWidget(widget); => not supported in Flutter Web for now
Uint8List? capturedImage = await webViewController!.takeScreenshot();
print('Bot : _generateImage() : capturedImage != null)');
_graphsImagesMap[imageId!] = capturedImage!;
//ShowCapturedWidget(context, capturedImage!);
return capturedImage;
}
void showFeedbackDialog() {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Please rate this answer"),
content: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 260.0),
child: Container(
//width: screenSize!.width / 2,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
child: Text("Good answer",
style: TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
elevation: 0,
),
onPressed: () {},
),
SizedBox(width: 30),
ElevatedButton(
child: Text("Partial answer",
style: TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
elevation: 0,
),
onPressed: () {},
),
SizedBox(width: 30),
ElevatedButton(
child: Text("Incorrect answer",
style: TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
elevation: 0,
),
onPressed: () {},
),
],
),
SizedBox(height: 50),
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
'Please provide additionnal feedback (optional)',
),
maxLines: null,
minLines: 5,
),
]),
),
),
),
//Text(DicInfoExtractedMap.toString(),),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Submit'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
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;
}
}
class OpenDataQnASource extends DataTableSource {
final List<DataRow> data;
OpenDataQnASource(this.data);
@override
int get rowCount => data.length;
@override
DataRow? getRow(int index) {
if (index < data.length) {
return data[index];
} else {
return null;
}
}
@override
bool get isRowCountApproximate => false;
@override
int get selectedRowCount => 0;
}