mirror of
https://github.com/Shchoholiev/shopping-assistant-mobile-client.git
synced 2025-04-11 09:28:51 +00:00
fixed message bugs and added the ability to view history
This commit is contained in:
parent
4754a2b07c
commit
747909fe42
@ -12,11 +12,8 @@ class AuthenticationService {
|
|||||||
|
|
||||||
late SharedPreferences prefs;
|
late SharedPreferences prefs;
|
||||||
|
|
||||||
AuthenticationService() {
|
|
||||||
SharedPreferences.getInstance().then((result) => {prefs = result});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getAccessToken() async {
|
Future<String> getAccessToken() async {
|
||||||
|
prefs = await SharedPreferences.getInstance();
|
||||||
var accessToken = prefs.getString('accessToken');
|
var accessToken = prefs.getString('accessToken');
|
||||||
var refreshToken = prefs.getString('refreshToken');
|
var refreshToken = prefs.getString('refreshToken');
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
// search_service.dart
|
// search_service.dart
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||||
import '../models/enums/search_event_type.dart';
|
import '../models/enums/search_event_type.dart';
|
||||||
import '../models/server_sent_event.dart';
|
import '../models/server_sent_event.dart';
|
||||||
import '../network/api_client.dart';
|
import '../network/api_client.dart';
|
||||||
import '../network/authentication_service.dart';
|
|
||||||
import '../screens/chat.dart';
|
import '../screens/chat.dart';
|
||||||
|
import 'authentication_service.dart';
|
||||||
|
|
||||||
const String startPersonalWishlistMutations = r'''
|
const String startPersonalWishlistMutations = r'''
|
||||||
mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) {
|
mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) {
|
||||||
@ -20,18 +19,17 @@ const String startPersonalWishlistMutations = r'''
|
|||||||
SearchEventType type = SearchEventType.message;
|
SearchEventType type = SearchEventType.message;
|
||||||
|
|
||||||
class SearchService {
|
class SearchService {
|
||||||
final AuthenticationService _authenticationService = AuthenticationService();
|
|
||||||
final ApiClient client = ApiClient();
|
final ApiClient client = ApiClient();
|
||||||
|
|
||||||
final _sseController = StreamController<ServerSentEvent>();
|
late final _sseController = StreamController<ServerSentEvent>();
|
||||||
|
|
||||||
Stream<ServerSentEvent> get sseStream => _sseController.stream;
|
Stream<ServerSentEvent> get sseStream => _sseController.stream;
|
||||||
|
|
||||||
Future<void> initializeAuthenticationService() async {
|
bool checkerForProduct() {
|
||||||
await _authenticationService.initialize();
|
return type == SearchEventType.product;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkerForProduct() {
|
bool checkerForSuggestion() {
|
||||||
return type == SearchEventType.product;
|
return type == SearchEventType.product;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,8 +58,7 @@ class SearchService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startPersonalWishlist(String message) async {
|
Future<String> startPersonalWishlist(String message) async {
|
||||||
await _authenticationService.initialize();
|
|
||||||
|
|
||||||
// Перевіряємо, чи вже створений wishlist
|
// Перевіряємо, чи вже створений wishlist
|
||||||
if (wishlistId == null) {
|
if (wishlistId == null) {
|
||||||
@ -88,18 +85,76 @@ class SearchService {
|
|||||||
await for (final chunk in sseStream) {
|
await for (final chunk in sseStream) {
|
||||||
print("Original chunk.data: ${chunk.event}");
|
print("Original chunk.data: ${chunk.event}");
|
||||||
final cleanedMessage = chunk.data.replaceAll(RegExp(r'(^"|"$)'), '');
|
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);
|
final event = ServerSentEvent(type, cleanedMessage);
|
||||||
_sseController.add(event);
|
_sseController.add(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return wishlistId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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<List<Message>> 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<dynamic> items = result['messagesPageFromPersonalWishlist']['items'];
|
||||||
|
|
||||||
|
final List<Message> messages = items.map((item) {
|
||||||
|
return Message(
|
||||||
|
text: item['text'],
|
||||||
|
role: item['role'],
|
||||||
|
isProduct: false,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,15 @@
|
|||||||
// search_screen.dart
|
// search_screen.dart
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||||
import 'package:shopping_assistant_mobile_client/network/search_service.dart';
|
import 'package:shopping_assistant_mobile_client/network/search_service.dart';
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
final String text;
|
final String text;
|
||||||
final bool isUser;
|
final String role;
|
||||||
bool isProduct;
|
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 {
|
class MessageBubble extends StatelessWidget {
|
||||||
@ -71,6 +72,7 @@ class ChatScreenState extends State<ChatScreen> {
|
|||||||
bool buttonsVisible = true;
|
bool buttonsVisible = true;
|
||||||
bool isSendButtonEnabled = false;
|
bool isSendButtonEnabled = false;
|
||||||
bool showButtonsContainer = true;
|
bool showButtonsContainer = true;
|
||||||
|
bool isWaitingForResponse = false;
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
late Widget appBarTitle;
|
late Widget appBarTitle;
|
||||||
|
|
||||||
@ -82,16 +84,48 @@ class ChatScreenState extends State<ChatScreen> {
|
|||||||
_searchService.sseStream.listen((event) {
|
_searchService.sseStream.listen((event) {
|
||||||
_handleSSEMessage(Message(text: '${event.data}'));
|
_handleSSEMessage(Message(text: '${event.data}'));
|
||||||
});
|
});
|
||||||
|
Future.delayed(Duration(milliseconds: 2000));
|
||||||
|
if(!wishlistId.isEmpty)
|
||||||
|
{
|
||||||
|
_loadPreviousMessages();
|
||||||
|
showButtonsContainer = false;
|
||||||
|
buttonsVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _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) {
|
void _handleSSEMessage(Message message) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
isWaitingForResponse = true;
|
||||||
final lastMessage = messages.isNotEmpty ? messages.last : null;
|
final lastMessage = messages.isNotEmpty ? messages.last : null;
|
||||||
message.isProduct = _searchService.checkerForProduct();
|
message.isProduct = _searchService.checkerForProduct();
|
||||||
|
message.isSuggestion = _searchService.checkerForSuggestion();
|
||||||
print("Product status: ${message.isProduct}");
|
print("Product status: ${message.isProduct}");
|
||||||
if (lastMessage != null && !lastMessage.isUser && !message.isUser) {
|
if (lastMessage != null && lastMessage.role != "User" && message.role != "User") {
|
||||||
final updatedMessage = Message(
|
final updatedMessage = Message(
|
||||||
text: "${lastMessage.text}${message.text}",
|
text: "${lastMessage.text}${message.text}",
|
||||||
|
role: "Application",
|
||||||
isProduct: message.isProduct);
|
isProduct: message.isProduct);
|
||||||
messages.removeLast();
|
messages.removeLast();
|
||||||
messages.add(updatedMessage);
|
messages.add(updatedMessage);
|
||||||
@ -99,6 +133,9 @@ class ChatScreenState extends State<ChatScreen> {
|
|||||||
messages.add(message);
|
messages.add(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setState(() {
|
||||||
|
isWaitingForResponse = false;
|
||||||
|
});
|
||||||
_scrollToBottom();
|
_scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +143,6 @@ class ChatScreenState extends State<ChatScreen> {
|
|||||||
final wishlistName = await _searchService.generateNameForPersonalWishlist(wishlistId);
|
final wishlistName = await _searchService.generateNameForPersonalWishlist(wishlistId);
|
||||||
if (wishlistName != null) {
|
if (wishlistName != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Оновіть назву чату з результатом методу generateNameForPersonalWishlist
|
|
||||||
appBarTitle = Text(wishlistName);
|
appBarTitle = Text(wishlistName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -116,30 +152,35 @@ class ChatScreenState extends State<ChatScreen> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
buttonsVisible = false;
|
buttonsVisible = false;
|
||||||
showButtonsContainer = false;
|
showButtonsContainer = false;
|
||||||
|
isWaitingForResponse = true;
|
||||||
});
|
});
|
||||||
await _searchService.initializeAuthenticationService();
|
wishlistId = await _searchService.startPersonalWishlist(message);
|
||||||
await _searchService.startPersonalWishlist(message);
|
|
||||||
updateChatTitle(_searchService.wishlistId.toString());
|
updateChatTitle(_searchService.wishlistId.toString());
|
||||||
_scrollToBottom();
|
_scrollToBottom();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isWaitingForResponse = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _sendMessageToAPI(String message) async {
|
Future<void> _sendMessageToAPI(String message)async {
|
||||||
setState(() {
|
setState(() {
|
||||||
buttonsVisible = false;
|
buttonsVisible = false;
|
||||||
showButtonsContainer = false;
|
showButtonsContainer = false;
|
||||||
|
isWaitingForResponse = true;
|
||||||
});
|
});
|
||||||
await _searchService.startPersonalWishlist(message);
|
await _searchService.sendMessages(message);
|
||||||
_scrollToBottom();
|
_scrollToBottom();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
messages.add(Message(text: message, isUser: true));
|
isWaitingForResponse = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendMessage() {
|
void _sendMessage() {
|
||||||
final message = _messageController.text;
|
final message = _messageController.text;
|
||||||
setState(() {
|
setState(() {
|
||||||
messages.add(Message(text: message, isUser: true));
|
messages.add(Message(text: message, role: "User"));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (wishlistId.isEmpty) {
|
if (wishlistId.isEmpty) {
|
||||||
@ -302,12 +343,36 @@ class ChatScreenState extends State<ChatScreen> {
|
|||||||
final message = messages[index];
|
final message = messages[index];
|
||||||
return MessageBubble(
|
return MessageBubble(
|
||||||
message: message.text,
|
message: message.text,
|
||||||
isOutgoing: message.isUser,
|
isOutgoing: message.role == "User",
|
||||||
isProduct: message.isProduct,
|
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(
|
Container(
|
||||||
margin: const EdgeInsets.all(8.0),
|
margin: const EdgeInsets.all(8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -316,7 +381,6 @@ class ChatScreenState extends State<ChatScreen> {
|
|||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _messageController,
|
controller: _messageController,
|
||||||
onChanged: (text) {
|
onChanged: (text) {
|
||||||
// Коли текст змінюється, оновлюємо стан кнопки
|
|
||||||
setState(() {
|
setState(() {
|
||||||
isSendButtonEnabled = text.isNotEmpty;
|
isSendButtonEnabled = text.isNotEmpty;
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_spinkit: ^5.0.0
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
Loading…
Reference in New Issue
Block a user