|
|
|
|
@ -31,6 +31,7 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
final TextEditingController _messageController = TextEditingController();
|
|
|
|
|
final ScrollController _scrollController = ScrollController();
|
|
|
|
|
AtlasInitiateChatResponse? atlasChatTokenResponse;
|
|
|
|
|
bool _isThinking = false;
|
|
|
|
|
|
|
|
|
|
// Speech to text
|
|
|
|
|
final SpeechToText _speechToText = SpeechToText();
|
|
|
|
|
@ -40,7 +41,6 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
void initState() {
|
|
|
|
|
// TODO: implement initState
|
|
|
|
|
super.initState();
|
|
|
|
|
print("Chat Bot Launched");
|
|
|
|
|
getChatSession();
|
|
|
|
|
_initSpeech();
|
|
|
|
|
}
|
|
|
|
|
@ -76,7 +76,9 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
try {
|
|
|
|
|
GenericResponseModel? res = await DashboardApiClient().getChatBotSession();
|
|
|
|
|
if (res != null) {
|
|
|
|
|
atlasChatTokenResponse = res.atlasInitiateChatResponse;
|
|
|
|
|
setState(() {
|
|
|
|
|
atlasChatTokenResponse = res.atlasInitiateChatResponse;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
Utils.handleException(ex, context, null);
|
|
|
|
|
@ -92,37 +94,23 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _sendMessage() async {
|
|
|
|
|
String text = _messageController.text.trim();
|
|
|
|
|
void _sendMessage({String? suggestionText}) async {
|
|
|
|
|
String text = suggestionText ?? _messageController.text.trim();
|
|
|
|
|
if (text.isEmpty || atlasChatTokenResponse == null) return;
|
|
|
|
|
|
|
|
|
|
// setState(() {
|
|
|
|
|
// _messages.add(ChatMessage(text: text, isUser: true, timestamp: DateTime.now()));
|
|
|
|
|
// _currentMode = ChatMode.chatActive;
|
|
|
|
|
// _messageController.clear();
|
|
|
|
|
// });
|
|
|
|
|
//
|
|
|
|
|
// // Simulate bot response
|
|
|
|
|
// Future.delayed(const Duration(milliseconds: 500), () {
|
|
|
|
|
// if (mounted) {
|
|
|
|
|
// setState(() {
|
|
|
|
|
// _messages.add(ChatMessage(text: "Hello there! How may I assist you today?", isUser: false, timestamp: DateTime.now()));
|
|
|
|
|
// });
|
|
|
|
|
// _scrollToBottom();
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
setState(() {
|
|
|
|
|
_messages.add(ChatMessage(text: text, isUser: true, timestamp: DateTime.now()));
|
|
|
|
|
_currentMode = ChatMode.chatActive;
|
|
|
|
|
_messageController.clear();
|
|
|
|
|
_isThinking = true;
|
|
|
|
|
});
|
|
|
|
|
_scrollToBottom();
|
|
|
|
|
GenericResponseModel? res = await DashboardApiClient().sendChatBotMessage(atlasChatTokenResponse: atlasChatTokenResponse!, atlasChatText: text);
|
|
|
|
|
if (res != null) {
|
|
|
|
|
res.atlasContinueChatResponseDetails;
|
|
|
|
|
if (mounted) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_isThinking = false;
|
|
|
|
|
_messages.add(
|
|
|
|
|
ChatMessage(
|
|
|
|
|
text: res.atlasContinueChatResponseDetails!.aiResponse!.structuredData!.answer ?? "",
|
|
|
|
|
@ -135,6 +123,9 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
_scrollToBottom();
|
|
|
|
|
}
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
setState(() {
|
|
|
|
|
_isThinking = false;
|
|
|
|
|
});
|
|
|
|
|
Utils.handleException(ex, context, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -150,8 +141,11 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _sendSuggestion(String text) {
|
|
|
|
|
_messageController.text = text;
|
|
|
|
|
_sendMessage();
|
|
|
|
|
if (atlasChatTokenResponse == null) {
|
|
|
|
|
Utils.showToast("Please wait, initializing chat...");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_sendMessage(suggestionText: text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
@ -199,8 +193,11 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
return ListView.separated(
|
|
|
|
|
controller: _scrollController,
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
|
|
|
itemCount: _messages.length,
|
|
|
|
|
itemCount: _messages.length + (_isThinking ? 1 : 0),
|
|
|
|
|
itemBuilder: (BuildContext context, int index) {
|
|
|
|
|
if (index == _messages.length && _isThinking) {
|
|
|
|
|
return _buildThinkingIndicator();
|
|
|
|
|
}
|
|
|
|
|
ChatMessage message = _messages[index];
|
|
|
|
|
return _buildMessageBubble(message);
|
|
|
|
|
},
|
|
|
|
|
@ -210,9 +207,28 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildThinkingIndicator() {
|
|
|
|
|
return Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
Container(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
gradient: const LinearGradient(colors: <Color>[Color(0xFF32D892), Color(0xFF259CB8)], begin: Alignment.centerLeft, end: Alignment.centerRight),
|
|
|
|
|
borderRadius: BorderRadius.circular(15),
|
|
|
|
|
boxShadow: <BoxShadow>[BoxShadow(color: const Color(0xFF000000).withValues(alpha: 0.0784), offset: const Offset(0, 0), blurRadius: 13, spreadRadius: 0)],
|
|
|
|
|
),
|
|
|
|
|
child: const ThinkingDotsAnimation(),
|
|
|
|
|
),
|
|
|
|
|
const Spacer(flex: 2),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _buildMessageBubble(ChatMessage message) {
|
|
|
|
|
bool isUser = message.isUser;
|
|
|
|
|
return Row(
|
|
|
|
|
Widget bubbleContent = Row(
|
|
|
|
|
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
@ -251,6 +267,11 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
if (!isUser) const Spacer(flex: 2),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!isUser) {
|
|
|
|
|
return AnimatedMessageBubble(child: bubbleContent);
|
|
|
|
|
}
|
|
|
|
|
return bubbleContent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String _formatTimestamp(DateTime timestamp) {
|
|
|
|
|
@ -300,21 +321,25 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
Expanded(
|
|
|
|
|
child: TextField(
|
|
|
|
|
controller: _messageController,
|
|
|
|
|
onSubmitted: (_) => _sendMessage(),
|
|
|
|
|
onSubmitted: _isThinking ? null : (_) => _sendMessage(),
|
|
|
|
|
enabled: !_isThinking,
|
|
|
|
|
maxLines: 1,
|
|
|
|
|
textAlignVertical: TextAlignVertical.center,
|
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
|
hintText: 'Type a message..',
|
|
|
|
|
hintStyle: TextStyle(color: MyColors.hintTextColor, fontSize: 14, fontWeight: FontWeight.normal),
|
|
|
|
|
decoration: InputDecoration(
|
|
|
|
|
hintText: _isThinking ? 'Waiting for response...' : 'Type a message..',
|
|
|
|
|
hintStyle: const TextStyle(color: MyColors.hintTextColor, fontSize: 14, fontWeight: FontWeight.normal),
|
|
|
|
|
border: InputBorder.none,
|
|
|
|
|
isDense: true,
|
|
|
|
|
contentPadding: EdgeInsets.symmetric(vertical: 10),
|
|
|
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 10),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
GestureDetector(
|
|
|
|
|
onTap: _speechEnabled ? (_speechToText.isNotListening ? _startListening : _stopListening) : null,
|
|
|
|
|
child: SvgPicture.asset("assets/icons/microphone.svg", colorFilter: ColorFilter.mode(_speechToText.isListening ? Colors.red : MyColors.darkTextColor, BlendMode.srcIn)),
|
|
|
|
|
onTap: _isThinking ? null : (_speechEnabled ? (_speechToText.isNotListening ? _startListening : _stopListening) : null),
|
|
|
|
|
child: SvgPicture.asset(
|
|
|
|
|
"assets/icons/microphone.svg",
|
|
|
|
|
colorFilter: ColorFilter.mode(_isThinking ? MyColors.hintTextColor : (_speechToText.isListening ? Colors.red : MyColors.darkTextColor), BlendMode.srcIn),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
@ -323,8 +348,13 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
const SizedBox(width: 6),
|
|
|
|
|
// Send button
|
|
|
|
|
GestureDetector(
|
|
|
|
|
onTap: _sendMessage,
|
|
|
|
|
child: Container(width: 48, height: 48, decoration: const BoxDecoration(color: MyColors.white, shape: BoxShape.circle), child: Center(child: SvgPicture.asset("assets/icons/send.svg"))),
|
|
|
|
|
onTap: _isThinking ? null : _sendMessage,
|
|
|
|
|
child: Container(
|
|
|
|
|
width: 48,
|
|
|
|
|
height: 48,
|
|
|
|
|
decoration: BoxDecoration(color: _isThinking ? MyColors.hintTextColor.withValues(alpha: 0.3) : MyColors.white, shape: BoxShape.circle),
|
|
|
|
|
child: Center(child: SvgPicture.asset("assets/icons/send.svg", colorFilter: _isThinking ? const ColorFilter.mode(MyColors.hintTextColor, BlendMode.srcIn) : null)),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
@ -332,3 +362,89 @@ class _ChatBotScreenState extends State<ChatBotScreen> {
|
|
|
|
|
).paddingOnly(left: 16, right: 16, bottom: 16);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ThinkingDotsAnimation extends StatefulWidget {
|
|
|
|
|
const ThinkingDotsAnimation({Key? key}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<ThinkingDotsAnimation> createState() => _ThinkingDotsAnimationState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _ThinkingDotsAnimationState extends State<ThinkingDotsAnimation> with SingleTickerProviderStateMixin {
|
|
|
|
|
late AnimationController _controller;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 1200))..repeat();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_controller.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return Row(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
const Text("Thinking", style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white)),
|
|
|
|
|
const SizedBox(width: 2),
|
|
|
|
|
AnimatedBuilder(
|
|
|
|
|
animation: _controller,
|
|
|
|
|
builder: (BuildContext context, Widget? child) {
|
|
|
|
|
int dotCount = (_controller.value * 4).floor() % 4;
|
|
|
|
|
String dots = '.' * dotCount;
|
|
|
|
|
return SizedBox(width: 18, child: Text(dots, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white)));
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class AnimatedMessageBubble extends StatefulWidget {
|
|
|
|
|
final Widget child;
|
|
|
|
|
|
|
|
|
|
const AnimatedMessageBubble({Key? key, required this.child}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
State<AnimatedMessageBubble> createState() => _AnimatedMessageBubbleState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _AnimatedMessageBubbleState extends State<AnimatedMessageBubble> with SingleTickerProviderStateMixin {
|
|
|
|
|
late AnimationController _controller;
|
|
|
|
|
late Animation<double> _fadeAnimation;
|
|
|
|
|
late Animation<Offset> _slideAnimation;
|
|
|
|
|
late Animation<double> _scaleAnimation;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
|
|
|
|
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 400));
|
|
|
|
|
|
|
|
|
|
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
|
|
|
|
|
|
|
|
|
|
_slideAnimation = Tween<Offset>(begin: const Offset(-0.3, 0.0), end: Offset.zero).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic));
|
|
|
|
|
|
|
|
|
|
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutBack));
|
|
|
|
|
|
|
|
|
|
_controller.forward();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_controller.dispose();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return SlideTransition(
|
|
|
|
|
position: _slideAnimation,
|
|
|
|
|
child: FadeTransition(opacity: _fadeAnimation, child: ScaleTransition(scale: _scaleAnimation, alignment: Alignment.centerLeft, child: widget.child)),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|