From 08a66e829f33bab396b35a750faeef05e4d5437b Mon Sep 17 00:00:00 2001 From: stasex Date: Tue, 21 Nov 2023 17:40:14 +0200 Subject: [PATCH 1/7] create screen for chat --- lib/main.dart | 20 ++-- lib/screens/chat.dart | 253 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 lib/screens/chat.dart diff --git a/lib/main.dart b/lib/main.dart index 1deb065..8add828 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,10 +2,12 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:graphql/client.dart'; import 'package:shopping_assistant_mobile_client/network/api_client.dart'; +import 'package:shopping_assistant_mobile_client/screens/chat.dart'; void main() { runApp(const MyApp()); } +final ApiClient client = ApiClient(); class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -34,7 +36,7 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + home: ChatScreen(), ); } } @@ -80,13 +82,13 @@ class _MyHomePageState extends State { '''; MutationOptions mutationOptions = MutationOptions( - document: gql(startPersonalWishlistMutations), - variables: const { - 'dto': { - 'firstMessageText': 'Gaming mechanical keyboard', - 'type': 'Product' - }, - } + document: gql(startPersonalWishlistMutations), + variables: const { + 'dto': { + 'firstMessageText': 'Gaming mechanical keyboard', + 'type': 'Product' + }, + } ); var result = await client.mutate(mutationOptions); @@ -155,4 +157,4 @@ class _MyHomePageState extends State { ), // This trailing comma makes auto-formatting nicer for build methods. ); } -} +} \ No newline at end of file diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart new file mode 100644 index 0000000..595d208 --- /dev/null +++ b/lib/screens/chat.dart @@ -0,0 +1,253 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:shopping_assistant_mobile_client/network/api_client.dart'; + +const String startPersonalWishlistMutations = r''' + mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { + startPersonalWishlist(dto: $dto) { + createdById, id, name, type + } + } +'''; + +const String sendMessageMutation = r''' + mutation sendMessage($wishlistId: ID!, $message: String!) { + sendMessage(wishlistId: $wishlistId, message: $message) { + // Опис того, що ви очікуєте від відповіді + } + } +'''; + +final ApiClient client = ApiClient(); + +class ChatScreen extends StatefulWidget { + @override + State createState() => ChatScreenState(); +} + +class MessageBubble extends StatelessWidget { + final String message; + + MessageBubble({required this.message}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerRight, + child: Container( + margin: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(10.0), + ), + child: Text( + message, + style: TextStyle(color: Colors.white), + ), + ), + ); + } +} + +class ChatScreenState extends State { + + final TextEditingController _messageController = TextEditingController(); + List messages = []; + bool buttonsVisible = true; + final ScrollController _scrollController = ScrollController(); + + + String wishlistId = ''; + + // Функція для старту першої вішлісту при відправці першого повідомлення + Future _startPersonalWishlist() async { + final options = MutationOptions( + document: gql(startPersonalWishlistMutations), + variables: { + 'dto': {'firstMessageText': messages.first, 'type': 'Product'}, + }, + ); + + final result = await client.mutate(options); + + if (result != null && result.containsKey('startPersonalWishlist')) { + setState(() { + wishlistId = result['startPersonalWishlist']['id']; + }); + } + } + + // Функція для відправки повідомлення до API + Future _sendMessageToAPI(String message) async { + final options = MutationOptions( + document: gql(sendMessageMutation), + variables: {'wishlistId': wishlistId, 'message': message}, + ); + + final result = await client.mutate(options); + + // Обробка результатів відправки повідомлення + if (result != null && result.containsKey('sendMessage')) { + // Отримання та обробка відповідей з GPT-4 + var sseStream = client.getServerSentEventStream( + 'api/productssearch/search/$wishlistId', + {'text': message}, + ); + + await for (var chunk in sseStream) { + print('${chunk.event}: ${chunk.data}'); + // Оновлення UI або збереження результатів, якщо необхідно + } + } + } + + // Функція для відправки повідомлення + void _sendMessage() { + String message = _messageController.text; + setState(() { + messages.insert(0, message); + }); + + if (wishlistId.isEmpty) { + // Якщо вішліст не створено, стартуємо його + _startPersonalWishlist().then((_) { + // Після створення вішлісту, відправляємо перше повідомлення до API + _sendMessageToAPI(message); + }); + } else { + // Якщо вішліст вже існує, відправляємо повідомлення до API + _sendMessageToAPI(message); + } + + _messageController.clear(); + _scrollController.animateTo( + 0.0, + duration: Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + + + void _showGiftNotAvailable() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Gift Functionality'), + content: Text('This function is currently unavailable.'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('New Chat'), + centerTitle: true, // Відцентрувати заголовок + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + // Обробник для кнопки "Назад" + print('Back button pressed'); + }, + ), + ), + body: Column( + children: [ + Visibility( + visible: buttonsVisible, + child: Align( + alignment: Alignment.topCenter, + child: Column( + children: [ + Text( + 'Choose an Option', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + // Обробник для кнопки "Product" + print('Product button pressed'); + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), // Закруглення країв + ), + primary: Colors.blue, // Колір кнопки + onPrimary: Colors.white, // Колір тексту на активній кнопці + ), + child: Text('Product'), + ), + SizedBox(width: 16.0), // Простір між кнопками + ElevatedButton( + onPressed: _showGiftNotAvailable, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), // Закруглення країв + ), + primary: Colors.white, // Колір кнопки "Gift" + onPrimary: Colors.black, // Колір тексту на активній кнопці + ), + child: Text('Gift'), + ), + ], + ), + ], + ), + ), + ), + Expanded( + child: ListView( + controller: _scrollController, + reverse: true, // Щоб список був у зворотньому порядку + children: [ + // Повідомлення користувача + for (var message in messages) + MessageBubble( + message: message, + ), + ], + ), + ), + Container( + margin: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _messageController, + decoration: InputDecoration( + hintText: 'Enter your message...', + ), + ), + ), + IconButton( + icon: Icon(Icons.send), + onPressed: _sendMessage, + ), + ], + ), + ), + ], + ), + ); + } + } \ No newline at end of file From 0393a70f88bf78415acc0273794f4738cdd2b878 Mon Sep 17 00:00:00 2001 From: stasex Date: Thu, 23 Nov 2023 13:51:10 +0200 Subject: [PATCH 2/7] create new logic for chat --- lib/network/search_service.dart | 49 ++++++++++ lib/screens/chat.dart | 168 ++++++++++++-------------------- 2 files changed, 111 insertions(+), 106 deletions(-) create mode 100644 lib/network/search_service.dart diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart new file mode 100644 index 0000000..b692f17 --- /dev/null +++ b/lib/network/search_service.dart @@ -0,0 +1,49 @@ +// search_service.dart + +import 'package:graphql_flutter/graphql_flutter.dart'; +import '../network/api_client.dart'; +import '../network/authentication_service.dart'; +import '../screens/chat.dart'; + +const String startPersonalWishlistMutations = r''' + mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { + startPersonalWishlist(dto: $dto) { + createdById, id, name, type + } + } +'''; + +class SearchService { + final AuthenticationService _authenticationService = AuthenticationService(); + final ApiClient client = ApiClient(); + + Future initializeAuthenticationService() async { + await _authenticationService.initialize(); + } + + Future startPersonalWishlist(String message, Function(Message) handleSSEMessage) async { + await _authenticationService.initialize(); + + final options = MutationOptions( + document: gql(startPersonalWishlistMutations), + variables: { + 'dto': {'firstMessageText': message, 'type': 'Product'}, + }, + ); + + final result = await client.mutate(options); + + if (result != null && result.containsKey('startPersonalWishlist')) { + final wishlistId = result['startPersonalWishlist']['id']; + final sseStream = client.getServerSentEventStream( + 'api/productssearch/search/$wishlistId', + {'text': message}, + ); + + await for (final chunk in sseStream) { + print('${chunk.event}: ${chunk.data}'); + handleSSEMessage(Message(text: '${chunk.event}: ${chunk.data}')); + } + } + } +} diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 595d208..865463c 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,135 +1,92 @@ -import 'dart:convert'; +// search_screen.dart import 'package:flutter/material.dart'; -import 'package:graphql_flutter/graphql_flutter.dart'; -import 'package:shopping_assistant_mobile_client/network/api_client.dart'; +import 'package:shopping_assistant_mobile_client/network/search_service.dart'; -const String startPersonalWishlistMutations = r''' - mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { - startPersonalWishlist(dto: $dto) { - createdById, id, name, type - } - } -'''; +class Message { + final String text; + final bool isUser; -const String sendMessageMutation = r''' - mutation sendMessage($wishlistId: ID!, $message: String!) { - sendMessage(wishlistId: $wishlistId, message: $message) { - // Опис того, що ви очікуєте від відповіді - } - } -'''; - -final ApiClient client = ApiClient(); - -class ChatScreen extends StatefulWidget { - @override - State createState() => ChatScreenState(); + Message({required this.text, this.isUser = false}); } class MessageBubble extends StatelessWidget { final String message; + final bool isOutgoing; - MessageBubble({required this.message}); + MessageBubble({required this.message, this.isOutgoing = true}); @override Widget build(BuildContext context) { return Align( - alignment: Alignment.centerRight, + alignment: isOutgoing ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( - color: Colors.blue, + color: isOutgoing ? Colors.blue : Colors.white, borderRadius: BorderRadius.circular(10.0), ), child: Text( message, - style: TextStyle(color: Colors.white), + style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), ), ), ); } } -class ChatScreenState extends State { +class ChatScreen extends StatefulWidget { + @override + State createState() => ChatScreenState(); +} +class ChatScreenState extends State { + final SearchService _searchService = SearchService(); + List messages = []; final TextEditingController _messageController = TextEditingController(); - List messages = []; bool buttonsVisible = true; final ScrollController _scrollController = ScrollController(); - String wishlistId = ''; - // Функція для старту першої вішлісту при відправці першого повідомлення - Future _startPersonalWishlist() async { - final options = MutationOptions( - document: gql(startPersonalWishlistMutations), - variables: { - 'dto': {'firstMessageText': messages.first, 'type': 'Product'}, - }, - ); - - final result = await client.mutate(options); - - if (result != null && result.containsKey('startPersonalWishlist')) { - setState(() { - wishlistId = result['startPersonalWishlist']['id']; - }); - } - } - - // Функція для відправки повідомлення до API - Future _sendMessageToAPI(String message) async { - final options = MutationOptions( - document: gql(sendMessageMutation), - variables: {'wishlistId': wishlistId, 'message': message}, - ); - - final result = await client.mutate(options); - - // Обробка результатів відправки повідомлення - if (result != null && result.containsKey('sendMessage')) { - // Отримання та обробка відповідей з GPT-4 - var sseStream = client.getServerSentEventStream( - 'api/productssearch/search/$wishlistId', - {'text': message}, - ); - - await for (var chunk in sseStream) { - print('${chunk.event}: ${chunk.data}'); - // Оновлення UI або збереження результатів, якщо необхідно - } - } - } - - // Функція для відправки повідомлення - void _sendMessage() { - String message = _messageController.text; + void _handleSSEMessage(Message message) { setState(() { - messages.insert(0, message); + messages.add(message); + }); + } + + Future _startPersonalWishlist(String message) async { + await _searchService.initializeAuthenticationService(); + await _searchService.startPersonalWishlist(message, _handleSSEMessage); + } + + Future _sendMessageToAPI(String message) async { + await _searchService.startPersonalWishlist(message, _handleSSEMessage); + + setState(() { + messages.add(Message(text: message, isUser: true)); + }); + } + + void _sendMessage() { + final message = _messageController.text; + setState(() { + messages.add(Message(text: message, isUser: true)); }); if (wishlistId.isEmpty) { - // Якщо вішліст не створено, стартуємо його - _startPersonalWishlist().then((_) { - // Після створення вішлісту, відправляємо перше повідомлення до API - _sendMessageToAPI(message); - }); + _startPersonalWishlist(message); } else { - // Якщо вішліст вже існує, відправляємо повідомлення до API _sendMessageToAPI(message); } _messageController.clear(); _scrollController.animateTo( - 0.0, + _scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 300), curve: Curves.easeOut, - ); - } - + );} void _showGiftNotAvailable() { showDialog( @@ -156,11 +113,10 @@ class ChatScreenState extends State { return Scaffold( appBar: AppBar( title: Text('New Chat'), - centerTitle: true, // Відцентрувати заголовок + centerTitle: true, leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () { - // Обробник для кнопки "Назад" print('Back button pressed'); }, ), @@ -182,29 +138,28 @@ class ChatScreenState extends State { children: [ ElevatedButton( onPressed: () { - // Обробник для кнопки "Product" print('Product button pressed'); }, style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), // Закруглення країв + borderRadius: BorderRadius.circular(10), ), - primary: Colors.blue, // Колір кнопки - onPrimary: Colors.white, // Колір тексту на активній кнопці + primary: Colors.blue, + onPrimary: Colors.white, ), child: Text('Product'), ), - SizedBox(width: 16.0), // Простір між кнопками + SizedBox(width: 16.0), ElevatedButton( onPressed: _showGiftNotAvailable, style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), // Закруглення країв + borderRadius: BorderRadius.circular(10), ), - primary: Colors.white, // Колір кнопки "Gift" - onPrimary: Colors.black, // Колір тексту на активній кнопці + primary: Colors.white, + onPrimary: Colors.black, ), child: Text('Gift'), ), @@ -215,16 +170,17 @@ class ChatScreenState extends State { ), ), Expanded( - child: ListView( + child: ListView.builder( controller: _scrollController, - reverse: true, // Щоб список був у зворотньому порядку - children: [ - // Повідомлення користувача - for (var message in messages) - MessageBubble( - message: message, - ), - ], + reverse: false, + itemCount: messages.length, + itemBuilder: (context, index) { + final message = messages[index]; + return MessageBubble( + message: message.text, + isOutgoing: message.isUser, + ); + }, ), ), Container( @@ -250,4 +206,4 @@ class ChatScreenState extends State { ), ); } - } \ No newline at end of file +} \ No newline at end of file From 7b3963fad7a8c9899bbddc745fd1b9677a3e7cb9 Mon Sep 17 00:00:00 2001 From: stasex Date: Thu, 23 Nov 2023 17:17:39 +0200 Subject: [PATCH 3/7] add normal messages --- lib/network/search_service.dart | 20 +++++++++++++++++--- lib/screens/chat.dart | 14 +++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index b692f17..e3b0320 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -1,6 +1,10 @@ // 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'; @@ -17,11 +21,15 @@ class SearchService { final AuthenticationService _authenticationService = AuthenticationService(); final ApiClient client = ApiClient(); + final _sseController = StreamController(); + + Stream get sseStream => _sseController.stream; + Future initializeAuthenticationService() async { await _authenticationService.initialize(); } - Future startPersonalWishlist(String message, Function(Message) handleSSEMessage) async { + Future startPersonalWishlist(String message) async { await _authenticationService.initialize(); final options = MutationOptions( @@ -40,10 +48,16 @@ class SearchService { {'text': message}, ); + StringBuffer fullMessage = StringBuffer(); // Використовуємо StringBuffer для зберігання повідомлення + await for (final chunk in sseStream) { - print('${chunk.event}: ${chunk.data}'); - handleSSEMessage(Message(text: '${chunk.event}: ${chunk.data}')); + fullMessage.write(chunk.data); // Додаємо чанк до повідомлення } + + final cleanedMessage = fullMessage.toString().replaceAll('"', ''); + + final event = ServerSentEvent(SearchEventType.message, cleanedMessage.toString().trim()); + _sseController.add(event); } } } diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 865463c..f75790a 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -24,7 +24,7 @@ class MessageBubble extends StatelessWidget { margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( - color: isOutgoing ? Colors.blue : Colors.white, + color: isOutgoing ? Colors.blue : Colors.white38, borderRadius: BorderRadius.circular(10.0), ), child: Text( @@ -50,6 +50,14 @@ class ChatScreenState extends State { String wishlistId = ''; + @override + void initState() { + super.initState(); + _searchService.sseStream.listen((event) { + _handleSSEMessage(Message(text: '${event.event}: ${event.data}')); + }); + } + void _handleSSEMessage(Message message) { setState(() { messages.add(message); @@ -58,11 +66,11 @@ class ChatScreenState extends State { Future _startPersonalWishlist(String message) async { await _searchService.initializeAuthenticationService(); - await _searchService.startPersonalWishlist(message, _handleSSEMessage); + await _searchService.startPersonalWishlist(message); } Future _sendMessageToAPI(String message) async { - await _searchService.startPersonalWishlist(message, _handleSSEMessage); + await _searchService.startPersonalWishlist(message); setState(() { messages.add(Message(text: message, isUser: true)); From 06ae71960ba7d4a175b6247e511d465d4f714f68 Mon Sep 17 00:00:00 2001 From: stasex Date: Thu, 23 Nov 2023 22:25:35 +0200 Subject: [PATCH 4/7] add logic for stream --- lib/network/search_service.dart | 55 ++++++++----- lib/screens/chat.dart | 137 +++++++++++++++++++++++++++++--- 2 files changed, 161 insertions(+), 31 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index e3b0320..3856656 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -17,6 +17,8 @@ const String startPersonalWishlistMutations = r''' } '''; +SearchEventType type = SearchEventType.message; + class SearchService { final AuthenticationService _authenticationService = AuthenticationService(); final ApiClient client = ApiClient(); @@ -29,35 +31,52 @@ class SearchService { await _authenticationService.initialize(); } + bool checkerForProduct() { + return type == SearchEventType.product; + } + + String? wishlistId; + Future startPersonalWishlist(String message) async { await _authenticationService.initialize(); - final options = MutationOptions( - document: gql(startPersonalWishlistMutations), - variables: { - 'dto': {'firstMessageText': message, 'type': 'Product'}, - }, - ); + // Перевіряємо, чи вже створений wishlist + if (wishlistId == null) { + final options = MutationOptions( + document: gql(startPersonalWishlistMutations), + variables: { + 'dto': {'firstMessageText': message, 'type': 'Product'}, + }, + ); - final result = await client.mutate(options); + final result = await client.mutate(options); - if (result != null && result.containsKey('startPersonalWishlist')) { - final wishlistId = result['startPersonalWishlist']['id']; + if (result != null && result.containsKey('startPersonalWishlist')) { + wishlistId = result['startPersonalWishlist']['id']; + } + } + + if (wishlistId != null) { final sseStream = client.getServerSentEventStream( 'api/productssearch/search/$wishlistId', {'text': message}, ); - StringBuffer fullMessage = StringBuffer(); // Використовуємо StringBuffer для зберігання повідомлення - await for (final chunk in sseStream) { - fullMessage.write(chunk.data); // Додаємо чанк до повідомлення + 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); } - - final cleanedMessage = fullMessage.toString().replaceAll('"', ''); - - final event = ServerSentEvent(SearchEventType.message, cleanedMessage.toString().trim()); - _sseController.add(event); } } -} +} \ No newline at end of file diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index f75790a..d021f6c 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -6,15 +6,17 @@ import 'package:shopping_assistant_mobile_client/network/search_service.dart'; class Message { final String text; final bool isUser; + bool isProduct; - Message({required this.text, this.isUser = false}); + Message({required this.text, this.isUser = false, this.isProduct = false}); } class MessageBubble extends StatelessWidget { final String message; final bool isOutgoing; + final bool isProduct; - MessageBubble({required this.message, this.isOutgoing = true}); + MessageBubble({required this.message, this.isOutgoing = true, this.isProduct = false}); @override Widget build(BuildContext context) { @@ -23,13 +25,34 @@ class MessageBubble extends StatelessWidget { child: Container( margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0), + constraints: BoxConstraints( + maxWidth: 300.0, // Максимальна ширина контейнера + ), decoration: BoxDecoration( - color: isOutgoing ? Colors.blue : Colors.white38, + color: isOutgoing ? Colors.blue : Colors.grey[200], borderRadius: BorderRadius.circular(10.0), ), - child: Text( - message, - style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + message, + style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), + ), + if (isProduct) // Виводимо кнопку тільки для повідомлень типу Product + ElevatedButton( + onPressed: () { + // Обробка натискання на кнопку "View Product" + print('View Product button pressed'); + }, + style: ElevatedButton.styleFrom( + primary: Colors.indigo, + onPrimary: Colors.white, + minimumSize: Size(300, 50) + ), + child: Text('View Product'), + ), + ], ), ), ); @@ -46,31 +69,55 @@ class ChatScreenState extends State { List messages = []; final TextEditingController _messageController = TextEditingController(); bool buttonsVisible = true; + bool isSendButtonEnabled = false; + bool showButtonsContainer = true; final ScrollController _scrollController = ScrollController(); String wishlistId = ''; - @override void initState() { super.initState(); _searchService.sseStream.listen((event) { - _handleSSEMessage(Message(text: '${event.event}: ${event.data}')); + _handleSSEMessage(Message(text: '${event.data}')); }); } void _handleSSEMessage(Message message) { setState(() { - messages.add(message); + final lastMessage = messages.isNotEmpty ? messages.last : null; + message.isProduct = _searchService.checkerForProduct(); + print("Product status: ${message.isProduct}"); + if (lastMessage != null && !lastMessage.isUser && !message.isUser) { + final updatedMessage = Message( + text: "${lastMessage.text}${message.text}", + isProduct: message.isProduct); + messages.removeLast(); + messages.add(updatedMessage); + } else { + messages.add(message); + } }); + _scrollToBottom(); } + Future _startPersonalWishlist(String message) async { + setState(() { + buttonsVisible = false; + showButtonsContainer = false; + }); await _searchService.initializeAuthenticationService(); await _searchService.startPersonalWishlist(message); + _scrollToBottom(); } Future _sendMessageToAPI(String message) async { + setState(() { + buttonsVisible = false; + showButtonsContainer = false; + }); await _searchService.startPersonalWishlist(message); + _scrollToBottom(); setState(() { messages.add(Message(text: message, isUser: true)); @@ -90,11 +137,16 @@ class ChatScreenState extends State { } _messageController.clear(); + _scrollToBottom(); + } + + void _scrollToBottom() { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 300), curve: Curves.easeOut, - );} + ); + } void _showGiftNotAvailable() { showDialog( @@ -149,7 +201,8 @@ class ChatScreenState extends State { print('Product button pressed'); }, style: ElevatedButton.styleFrom( - padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + padding: EdgeInsets.symmetric( + horizontal: 30, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -162,7 +215,8 @@ class ChatScreenState extends State { ElevatedButton( onPressed: _showGiftNotAvailable, style: ElevatedButton.styleFrom( - padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + padding: EdgeInsets.symmetric( + horizontal: 30, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -177,6 +231,56 @@ class ChatScreenState extends State { ), ), ), + SizedBox(height: 16.0), // Відступ вниз + Visibility( + visible: showButtonsContainer, + child: Container( + margin: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () { + _messageController.text = 'Christmas gift🎁'; + _sendMessage(); + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + primary: Colors.white, + onPrimary: Colors.blue, + side: BorderSide(color: Colors.blue, width: 2.0), + ), + child: Text('Christmas gift🎁', style: TextStyle(color: Colors.grey)), + ), + ), + Container( + margin: EdgeInsets.all(8), + child: ElevatedButton( + onPressed: () { + _messageController.text = 'Birthday gift🎉'; + _sendMessage(); + }, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + primary: Colors.white, + onPrimary: Colors.blue, + side: BorderSide(color: Colors.blue, width: 2.0), + ), + child: Text('Birthday gift🎉', style: TextStyle(color: Colors.grey)), + ), + ), + ], + ), + ), + ), Expanded( child: ListView.builder( controller: _scrollController, @@ -187,6 +291,7 @@ class ChatScreenState extends State { return MessageBubble( message: message.text, isOutgoing: message.isUser, + isProduct: message.isProduct, ); }, ), @@ -198,6 +303,12 @@ class ChatScreenState extends State { Expanded( child: TextField( controller: _messageController, + onChanged: (text) { + // Коли текст змінюється, оновлюємо стан кнопки + setState(() { + isSendButtonEnabled = text.isNotEmpty; + }); + }, decoration: InputDecoration( hintText: 'Enter your message...', ), @@ -205,7 +316,7 @@ class ChatScreenState extends State { ), IconButton( icon: Icon(Icons.send), - onPressed: _sendMessage, + onPressed: isSendButtonEnabled ? _sendMessage : null, ), ], ), From 4754a2b07c93bbe79a6fa846d74cd72f2fff6a56 Mon Sep 17 00:00:00 2001 From: stasex Date: Fri, 24 Nov 2023 00:16:34 +0200 Subject: [PATCH 5/7] fig for name changer --- lib/network/search_service.dart | 23 +++++++++++++++++++++++ lib/screens/chat.dart | 18 +++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index 3856656..71a7e25 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -37,6 +37,29 @@ class SearchService { String? wishlistId; + Future generateNameForPersonalWishlist(String wishlistId) async { + final options = MutationOptions( + document: gql(''' + mutation GenerateNameForPersonalWishlist(\$wishlistId: String!) { + generateNameForPersonalWishlist(wishlistId: \$wishlistId) { + id + name + } + } + '''), + variables: {'wishlistId': wishlistId}, + ); + + final result = await client.mutate(options); + + if (result != null && result.containsKey('generateNameForPersonalWishlist')) { + final name = result['generateNameForPersonalWishlist']['name']; + return name; + } + + return null; + } + Future startPersonalWishlist(String message) async { await _authenticationService.initialize(); diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index d021f6c..53f9afd 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -46,8 +46,8 @@ class MessageBubble extends StatelessWidget { print('View Product button pressed'); }, style: ElevatedButton.styleFrom( - primary: Colors.indigo, - onPrimary: Colors.white, + primary: Colors.indigo, + onPrimary: Colors.white, minimumSize: Size(300, 50) ), child: Text('View Product'), @@ -72,11 +72,13 @@ class ChatScreenState extends State { bool isSendButtonEnabled = false; bool showButtonsContainer = true; final ScrollController _scrollController = ScrollController(); + late Widget appBarTitle; String wishlistId = ''; void initState() { super.initState(); + appBarTitle = Text('New Chat'); _searchService.sseStream.listen((event) { _handleSSEMessage(Message(text: '${event.data}')); }); @@ -100,6 +102,15 @@ class ChatScreenState extends State { _scrollToBottom(); } + Future updateChatTitle(String wishlistId) async { + final wishlistName = await _searchService.generateNameForPersonalWishlist(wishlistId); + if (wishlistName != null) { + setState(() { + // Оновіть назву чату з результатом методу generateNameForPersonalWishlist + appBarTitle = Text(wishlistName); + }); + } + } Future _startPersonalWishlist(String message) async { setState(() { @@ -108,6 +119,7 @@ class ChatScreenState extends State { }); await _searchService.initializeAuthenticationService(); await _searchService.startPersonalWishlist(message); + updateChatTitle(_searchService.wishlistId.toString()); _scrollToBottom(); } @@ -172,7 +184,7 @@ class ChatScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('New Chat'), + title: appBarTitle, centerTitle: true, leading: IconButton( icon: Icon(Icons.arrow_back), From 747909fe4237a744188b330d851bb4e49ef2f347 Mon Sep 17 00:00:00 2001 From: stasex Date: Fri, 24 Nov 2023 22:19:01 +0200 Subject: [PATCH 6/7] 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. From 8ff63054b70b54d5b41c3214012815c3bfc2058a Mon Sep 17 00:00:00 2001 From: stasex Date: Sun, 26 Nov 2023 16:23:31 +0200 Subject: [PATCH 7/7] added logger and fixed the sending of the first message --- lib/network/search_service.dart | 25 +++++------------------- lib/screens/chat.dart | 34 ++++++++++++++++++--------------- pubspec.yaml | 1 + 3 files changed, 25 insertions(+), 35 deletions(-) diff --git a/lib/network/search_service.dart b/lib/network/search_service.dart index ab54b9a..2197852 100644 --- a/lib/network/search_service.dart +++ b/lib/network/search_service.dart @@ -1,12 +1,11 @@ // search_service.dart import 'dart:async'; - +import 'package:logger/logger.dart'; 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 '../screens/chat.dart'; -import 'authentication_service.dart'; const String startPersonalWishlistMutations = r''' mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { @@ -16,6 +15,8 @@ const String startPersonalWishlistMutations = r''' } '''; +var logger = Logger(); + SearchEventType type = SearchEventType.message; class SearchService { @@ -60,12 +61,11 @@ class SearchService { Future startPersonalWishlist(String message) async { - // Перевіряємо, чи вже створений wishlist if (wishlistId == null) { final options = MutationOptions( document: gql(startPersonalWishlistMutations), variables: { - 'dto': {'firstMessageText': message, 'type': 'Product'}, + 'dto': {'firstMessageText': "What are you looking for?", 'type': 'Product'}, }, ); @@ -75,21 +75,6 @@ class SearchService { wishlistId = result['startPersonalWishlist']['id']; } } - - 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(type, cleanedMessage); - _sseController.add(event); - } - } return wishlistId.toString(); } @@ -133,7 +118,7 @@ class SearchService { }, ); - print("DOCUMENT: ${options.document}"); + logger.d("DOCUMENT: ${options.document}"); final result = await client.query(options); diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index 5955da0..a12f0c0 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -1,6 +1,7 @@ // search_screen.dart import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:logger/logger.dart'; import 'package:shopping_assistant_mobile_client/network/search_service.dart'; class Message { @@ -27,7 +28,7 @@ class MessageBubble extends StatelessWidget { margin: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0), constraints: BoxConstraints( - maxWidth: 300.0, // Максимальна ширина контейнера + maxWidth: 300.0, ), decoration: BoxDecoration( color: isOutgoing ? Colors.blue : Colors.grey[200], @@ -40,10 +41,9 @@ class MessageBubble extends StatelessWidget { message, style: TextStyle(color: isOutgoing ? Colors.white : Colors.black), ), - if (isProduct) // Виводимо кнопку тільки для повідомлень типу Product + if (isProduct) ElevatedButton( onPressed: () { - // Обробка натискання на кнопку "View Product" print('View Product button pressed'); }, style: ElevatedButton.styleFrom( @@ -66,6 +66,7 @@ class ChatScreen extends StatefulWidget { } class ChatScreenState extends State { + var logger = Logger(); final SearchService _searchService = SearchService(); List messages = []; final TextEditingController _messageController = TextEditingController(); @@ -96,22 +97,21 @@ class ChatScreenState extends State { 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'); + logger.d('Previous Messages: $previousMessages'); for(final message in messages) { - print("MESSAGES TEXT: ${message.text}"); - print("MESSAGES ROLE: ${message.role}"); + logger.d("MESSAGES TEXT: ${message.text}"); + logger.d("MESSAGES ROLE: ${message.role}"); } } catch (error) { - print('Error loading previous messages: $error'); + logger.d('Error loading previous messages: $error'); } } @@ -121,7 +121,7 @@ class ChatScreenState extends State { final lastMessage = messages.isNotEmpty ? messages.last : null; message.isProduct = _searchService.checkerForProduct(); message.isSuggestion = _searchService.checkerForSuggestion(); - print("Product status: ${message.isProduct}"); + logger.d("Product status: ${message.isProduct}"); if (lastMessage != null && lastMessage.role != "User" && message.role != "User") { final updatedMessage = Message( text: "${lastMessage.text}${message.text}", @@ -155,7 +155,8 @@ class ChatScreenState extends State { isWaitingForResponse = true; }); wishlistId = await _searchService.startPersonalWishlist(message); - updateChatTitle(_searchService.wishlistId.toString()); + await _sendMessageToAPI(message); + await updateChatTitle(_searchService.wishlistId.toString()); _scrollToBottom(); setState(() { @@ -179,13 +180,17 @@ class ChatScreenState extends State { void _sendMessage() { final message = _messageController.text; - setState(() { - messages.add(Message(text: message, role: "User")); - }); if (wishlistId.isEmpty) { + setState(() { + messages.add(Message(text: "What are you looking for?", role: "Application")); + messages.add(Message(text: message, role: "User")); + }); _startPersonalWishlist(message); } else { + setState(() { + messages.add(Message(text: message, role: "User")); + }); _sendMessageToAPI(message); } @@ -284,7 +289,7 @@ class ChatScreenState extends State { ), ), ), - SizedBox(height: 16.0), // Відступ вниз + SizedBox(height: 16.0), Visibility( visible: showButtonsContainer, child: Container( @@ -372,7 +377,6 @@ class ChatScreenState extends State { ], ), ), - // Поле введення повідомлень Container( margin: const EdgeInsets.all(8.0), child: Row( diff --git a/pubspec.yaml b/pubspec.yaml index e862f53..041a786 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: flutter: sdk: flutter flutter_spinkit: ^5.0.0 + logger: ^2.0.2+1 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.