using System.Collections.ObjectModel; using System.Linq.Expressions; using System.Runtime.CompilerServices; using Newtonsoft.Json.Linq; using ShoppingAssistantApi.Application.IRepositories; using ShoppingAssistantApi.Application.IServices; using ShoppingAssistantApi.Application.Models.CreateDtos; using ShoppingAssistantApi.Application.Models.Dtos; using ShoppingAssistantApi.Application.Models.OpenAi; using ShoppingAssistantApi.Application.Models.ProductSearch; using ShoppingAssistantApi.Domain.Entities; using ShoppingAssistantApi.Domain.Enums; namespace ShoppingAssistantApi.Infrastructure.Services; public class ProductService : IProductService { private readonly IWishlistsService _wishlistsService; private readonly IOpenAiService _openAiService; public ProductService(IOpenAiService openAiService, IWishlistsService wishlistsService) { _openAiService = openAiService; _wishlistsService = wishlistsService; } public IAsyncEnumerable SearchProductAsync(string wishlistId, MessageCreateDto message, CancellationToken cancellationToken) { // get all messages from wishlist throw new NotImplementedException(); } public async IAsyncEnumerable<(List ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken) { List messages = new List() { new OpenAiMessage() { Role = OpenAiRole.User, Content = PromptForProductSearch(message.Text) } }; var chatRequest = new ChatCompletionRequest { Messages = messages }; await foreach (var response in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken)) { var openAiContent = JObject.Parse(response); var productNames = openAiContent["Name"]?.ToObject>() ?? new List(); WishlistCreateDto newWishlist = new WishlistCreateDto() { Type = "Product", FirstMessageText = message.Text }; var resultWishlistTask = _wishlistsService.StartPersonalWishlistAsync(newWishlist, cancellationToken); var resultWishlist = await resultWishlistTask; yield return (productNames, resultWishlist); } } public async IAsyncEnumerable GetProductFromSearch(Message message, [EnumeratorCancellation] CancellationToken cancellationToken) { List messages = new List() { new OpenAiMessage() { Role = OpenAiRole.User, Content = PromptForProductSearchWithQuestion(message.Text) } }; var chatRequest = new ChatCompletionRequest { Messages = messages }; await foreach (var response in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken)) { var openAiContent = JObject.Parse(response); var productNames = openAiContent["Name"]?.ToObject>(); if (productNames != null && productNames.Any()) { foreach (var productName in productNames) { yield return productName.Name; } } else { var questions = openAiContent["AdditionalQuestion"]?.ToObject>() ?? new List(); foreach (var question in questions) { yield return question.QuestionText; } } } } public async IAsyncEnumerable GetRecommendationsForProductFromSearchStream(Message message, CancellationToken cancellationToken) { List messages = new List() { new OpenAiMessage() { Role = OpenAiRole.User, Content = PromptForRecommendationsForProductSearch(message.Text) } }; var chatRequest = new ChatCompletionRequest { Messages = messages }; await foreach (var response in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken)) { var openAiContent = JObject.Parse(response); var recommendations = openAiContent["Recommendation"]?.ToObject>() ?? new List(); foreach (var recommendation in recommendations) { yield return recommendation; } } } public string PromptForProductSearch(string message) { string promptForSearch = "Return information in JSON. " + "\nProvide information, only that indicated in the type of answer, namely only the name. " + "\nAsk additional questions to the user if there is not enough information. " + "\nIf there are several answer options, list them. " + "\nYou don't need to display questions and products together! " + "\nDo not output any text other than JSON!!! " + $"\n\nQuestion: {message} " + $"\nType of answer: Question:[] " + $"\n\nif there are no questions, then just display the products " + $"\nType of answer: Name:"; return promptForSearch; } public string PromptForRecommendationsForProductSearch(string message) { string promptForSearch = "Return information in JSON. " + "\nProvide only information indicated in the type of answer, namely only the recommendation. " + "\nIf there are several answer options, list them. " + "\nDo not output any text other than JSON." + $"\n\nGive recommendations for this question: {message} " + "\nType of answer: " + "\n\nRecommendation :"; return promptForSearch; } public string PromptForProductSearchWithQuestion(string message) { string promptForSearch = "Return information in JSON. " + "\nAsk additional questions to the user if there is not enough information." + "\nIf there are several answer options, list them. " + "\nYou don't need to display questions and products together!" + "\nDo not output any text other than JSON!!!" + $"\n\nQuestion: {message}" + "\n\nif you can ask questions to clarify the choice, then ask them" + "\nType of answer:" + "\nAdditionalQuestion:[]" + "\n\nif there are no questions, then just display the products" + "\nType of answer:" + "\nName:"; return promptForSearch; } }