From 747909fe4237a744188b330d851bb4e49ef2f347 Mon Sep 17 00:00:00 2001 From: stasex Date: Fri, 24 Nov 2023 22:19:01 +0200 Subject: [PATCH] fixed message bugs and added the ability to view history --- lib/network/authentication_service.dart | 5 +- lib/network/search_service.dart | 89 +++++++++++++++++++----- lib/screens/chat.dart | 90 +++++++++++++++++++++---- pubspec.yaml | 2 +- 4 files changed, 151 insertions(+), 35 deletions(-) diff --git a/lib/network/authentication_service.dart b/lib/network/authentication_service.dart index be65417..e67aed7 100644 --- a/lib/network/authentication_service.dart +++ b/lib/network/authentication_service.dart @@ -12,11 +12,8 @@ class AuthenticationService { late SharedPreferences prefs; - AuthenticationService() { - SharedPreferences.getInstance().then((result) => {prefs = result}); - } - Future getAccessToken() async { + prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString('accessToken'); var refreshToken = prefs.getString('refreshToken'); diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index 71a7e25..ab54b9a 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -1,13 +1,12 @@ // search_service.dart - import 'dart:async'; import 'package:graphql_flutter/graphql_flutter.dart'; import '../models/enums/search_event_type.dart'; import '../models/server_sent_event.dart'; import '../network/api_client.dart'; -import '../network/authentication_service.dart'; import '../screens/chat.dart'; +import 'authentication_service.dart'; const String startPersonalWishlistMutations = r''' mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { @@ -20,18 +19,17 @@ const String startPersonalWishlistMutations = r''' SearchEventType type = SearchEventType.message; class SearchService { - final AuthenticationService _authenticationService = AuthenticationService(); final ApiClient client = ApiClient(); - final _sseController = StreamController(); + late final _sseController = StreamController(); Stream get sseStream => _sseController.stream; - Future initializeAuthenticationService() async { - await _authenticationService.initialize(); + bool checkerForProduct() { + return type == SearchEventType.product; } - bool checkerForProduct() { + bool checkerForSuggestion() { return type == SearchEventType.product; } @@ -60,8 +58,7 @@ class SearchService { return null; } - Future startPersonalWishlist(String message) async { - await _authenticationService.initialize(); + Future startPersonalWishlist(String message) async { // Перевіряємо, чи вже створений wishlist if (wishlistId == null) { @@ -88,18 +85,76 @@ class SearchService { await for (final chunk in sseStream) { print("Original chunk.data: ${chunk.event}"); final cleanedMessage = chunk.data.replaceAll(RegExp(r'(^"|"$)'), ''); - if(chunk.event == SearchEventType.message) - { - type = SearchEventType.message; - } - if(chunk.event == SearchEventType.product) - { - type = SearchEventType.product; - } final event = ServerSentEvent(type, cleanedMessage); _sseController.add(event); } } + return wishlistId.toString(); + } + + Future sendMessages(String message) async { + + if (wishlistId != null) { + final sseStream = client.getServerSentEventStream( + 'api/productssearch/search/$wishlistId', + {'text': message}, + ); + + await for (final chunk in sseStream) { + print("Original chunk.data: ${chunk.event}"); + final cleanedMessage = chunk.data.replaceAll(RegExp(r'(^"|"$)'), ''); + + final event = ServerSentEvent(chunk.event, cleanedMessage); + type = chunk.event; + _sseController.add(event); + } + } + } + + Future> getMessagesFromPersonalWishlist(String wishlistIdPar, int pageNumber, int pageSize) async { + final options = QueryOptions( + document: gql(''' + query MessagesPageFromPersonalWishlist(\$wishlistId: String!, \$pageNumber: Int!, \$pageSize: Int!) { + messagesPageFromPersonalWishlist(wishlistId: \$wishlistId, pageNumber: \$pageNumber, pageSize: \$pageSize) { + items { + id + text + role + createdById + } + } + } + '''), + variables: { + 'wishlistId': wishlistIdPar, + 'pageNumber': pageNumber, + 'pageSize': pageSize, + }, + ); + + print("DOCUMENT: ${options.document}"); + + final result = await client.query(options); + + print("RESULT: ${result}"); + print(result); + if (result != null && + result.containsKey('messagesPageFromPersonalWishlist') && + result['messagesPageFromPersonalWishlist'] != null && + result['messagesPageFromPersonalWishlist']['items'] != null) { + final List items = result['messagesPageFromPersonalWishlist']['items']; + + final List messages = items.map((item) { + return Message( + text: item['text'], + role: item['role'], + isProduct: false, + ); + }).toList(); + + return messages; + } + return []; } } \ No newline at end of file diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 53f9afd..5955da0 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,14 +1,15 @@ // search_screen.dart - import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:shopping_assistant_mobile_client/network/search_service.dart'; class Message { final String text; - final bool isUser; + final String role; bool isProduct; + bool isSuggestion; - Message({required this.text, this.isUser = false, this.isProduct = false}); + Message({required this.text, this.role = "", this.isProduct = false, this.isSuggestion = false}); } class MessageBubble extends StatelessWidget { @@ -71,6 +72,7 @@ class ChatScreenState extends State { bool buttonsVisible = true; bool isSendButtonEnabled = false; bool showButtonsContainer = true; + bool isWaitingForResponse = false; final ScrollController _scrollController = ScrollController(); late Widget appBarTitle; @@ -82,16 +84,48 @@ class ChatScreenState extends State { _searchService.sseStream.listen((event) { _handleSSEMessage(Message(text: '${event.data}')); }); + Future.delayed(Duration(milliseconds: 2000)); + if(!wishlistId.isEmpty) + { + _loadPreviousMessages(); + showButtonsContainer = false; + buttonsVisible = false; + } + } + + Future _loadPreviousMessages() async { + final pageNumber = 1; + final pageSize = 200; + print('Previous Messages:'); + try { + final previousMessages = await _searchService.getMessagesFromPersonalWishlist("6560b4c210686c50ed4b9fec", pageNumber, pageSize); + final reversedMessages = previousMessages.reversed.toList(); + setState(() { + messages.addAll(reversedMessages); + }); + print('Previous Messages: $previousMessages'); + + for(final message in messages) + { + print("MESSAGES TEXT: ${message.text}"); + print("MESSAGES ROLE: ${message.role}"); + } + } catch (error) { + print('Error loading previous messages: $error'); + } } void _handleSSEMessage(Message message) { setState(() { + isWaitingForResponse = true; final lastMessage = messages.isNotEmpty ? messages.last : null; message.isProduct = _searchService.checkerForProduct(); + message.isSuggestion = _searchService.checkerForSuggestion(); print("Product status: ${message.isProduct}"); - if (lastMessage != null && !lastMessage.isUser && !message.isUser) { + if (lastMessage != null && lastMessage.role != "User" && message.role != "User") { final updatedMessage = Message( text: "${lastMessage.text}${message.text}", + role: "Application", isProduct: message.isProduct); messages.removeLast(); messages.add(updatedMessage); @@ -99,6 +133,9 @@ class ChatScreenState extends State { messages.add(message); } }); + setState(() { + isWaitingForResponse = false; + }); _scrollToBottom(); } @@ -106,7 +143,6 @@ class ChatScreenState extends State { final wishlistName = await _searchService.generateNameForPersonalWishlist(wishlistId); if (wishlistName != null) { setState(() { - // Оновіть назву чату з результатом методу generateNameForPersonalWishlist appBarTitle = Text(wishlistName); }); } @@ -116,30 +152,35 @@ class ChatScreenState extends State { setState(() { buttonsVisible = false; showButtonsContainer = false; + isWaitingForResponse = true; }); - await _searchService.initializeAuthenticationService(); - await _searchService.startPersonalWishlist(message); + wishlistId = await _searchService.startPersonalWishlist(message); updateChatTitle(_searchService.wishlistId.toString()); _scrollToBottom(); + + setState(() { + isWaitingForResponse = false; + }); } - Future _sendMessageToAPI(String message) async { + Future _sendMessageToAPI(String message)async { setState(() { buttonsVisible = false; showButtonsContainer = false; + isWaitingForResponse = true; }); - await _searchService.startPersonalWishlist(message); + await _searchService.sendMessages(message); _scrollToBottom(); setState(() { - messages.add(Message(text: message, isUser: true)); + isWaitingForResponse = false; }); } void _sendMessage() { final message = _messageController.text; setState(() { - messages.add(Message(text: message, isUser: true)); + messages.add(Message(text: message, role: "User")); }); if (wishlistId.isEmpty) { @@ -302,12 +343,36 @@ class ChatScreenState extends State { final message = messages[index]; return MessageBubble( message: message.text, - isOutgoing: message.isUser, + isOutgoing: message.role == "User", isProduct: message.isProduct, ); }, ), ), + if (isWaitingForResponse) + SpinKitFadingCircle( + color: Colors.blue, + size: 25.0, + ), + if (messages.any((message) => message.isSuggestion)) + Container( + padding: EdgeInsets.all(8.0), + color: Colors.grey[300], + child: Row( + children: [ + Icon(Icons.lightbulb), + SizedBox(width: 8.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: messages + .where((message) => message.isSuggestion) + .map((message) => Text(message.text)) + .toList(), + ), + ], + ), + ), + // Поле введення повідомлень Container( margin: const EdgeInsets.all(8.0), child: Row( @@ -316,7 +381,6 @@ class ChatScreenState extends State { child: TextField( controller: _messageController, onChanged: (text) { - // Коли текст змінюється, оновлюємо стан кнопки setState(() { isSendButtonEnabled = text.isNotEmpty; }); diff --git a/pubspec.yaml b/pubspec.yaml index 0840073..e862f53 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,7 +30,7 @@ environment: dependencies: flutter: sdk: flutter - + flutter_spinkit: ^5.0.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.