From 0efb2ac75de816cedd481d62d812e9feb76186d1 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Sun, 22 Dec 2024 15:49:43 -0300 Subject: [PATCH] =?UTF-8?q?fix:=20vers=C3=A3o=20funcional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ChatApi.csproj.user | 2 + ChatHistoryService.cs | 10 +- Controllers/ChatController.cs | 64 +++---- Controllers/ChatController.cs~RF39ccb1.TMP | 68 ------- Controllers/LoginController.cs | 121 +++++++++++++ Controllers/LoginRequest.cs | 9 + Controllers/UserRequest.cs | 16 ++ Data/TextData.cs | 73 ++++++++ Data/UserDataRepository.cs | 45 +++++ DomvsDatabaseSettings.cs | 2 + Models/UserData.cs | 34 ++-- Program.cs | 86 +++++++-- Services/Bot/ActionCreateCall.cs | 8 +- Services/Bot/ChatBotRHCall.cs | 23 ++- Services/Bot/Structs/4c0w1xuj.h1l~ | 171 ++++++++++++++++++ Services/Bot/Structs/ChatBot.cs | 104 +++++++++-- Services/Bot/Structs/Question.cs | 4 +- Services/Classifier/ClassifierSate.cs | 1 + Services/Crypt/CryptUtil.cs | 66 +++++++ Services/Emails/EmailValidate.cs | 24 +++ Services/ResponseService/IResponseService.cs | 2 +- .../ResponseService/ResponseBotRHService.cs | 63 ++++--- .../ResponseService/ResponseChatService.cs | 2 +- .../ResponseService/ResponseCompanyService.cs | 2 +- Services/TextClassifier.cs | 8 + appsettings.json | 16 +- 26 files changed, 827 insertions(+), 197 deletions(-) delete mode 100644 Controllers/ChatController.cs~RF39ccb1.TMP create mode 100644 Controllers/LoginController.cs create mode 100644 Controllers/LoginRequest.cs create mode 100644 Controllers/UserRequest.cs create mode 100644 Data/TextData.cs create mode 100644 Data/UserDataRepository.cs create mode 100644 Services/Bot/Structs/4c0w1xuj.h1l~ create mode 100644 Services/Crypt/CryptUtil.cs create mode 100644 Services/Emails/EmailValidate.cs diff --git a/ChatApi.csproj.user b/ChatApi.csproj.user index 983ecfc..e5a2ec0 100644 --- a/ChatApi.csproj.user +++ b/ChatApi.csproj.user @@ -2,6 +2,8 @@ http + ApiControllerEmptyScaffolder + root/Common/Api ProjectDebugger diff --git a/ChatHistoryService.cs b/ChatHistoryService.cs index 5eee0ea..c72abc5 100644 --- a/ChatHistoryService.cs +++ b/ChatHistoryService.cs @@ -50,7 +50,7 @@ namespace ChatApi else { var msg = new List(); - PromptIan(msg); + PromptLiliana(msg); string json = JsonSerializer.Serialize(msg); var history = new ChatHistory(JsonSerializer.Deserialize>(json)); _keyValues[sessionId] = history; @@ -72,10 +72,12 @@ namespace ChatApi msg.Add(new ChatMessageContent(AuthorRole.User, "Responda sempre em portugues do Brasil as minhas perguntas.")); } - public void PromptIan(List msg) + public void PromptLiliana(List msg) { - msg.Add(new ChatMessageContent(AuthorRole.System, "Seu nome é Ian, um assistente ajuda empresas de Consultoria e RH. ")); - msg.Add(new ChatMessageContent(AuthorRole.System, "Você responde sempre em português do Brasil.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Seu nome é LiliAna, um assistente virtual da Domvs It (Consultoria e RH).")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Você responde sempre em português do Brasil e fala sobre serviços prestados pela Domvs iT.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Você consegue fazer solicitações no portal do RH da Domvs iT.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Você responde perguntas voltadas ao mercado de trabalho de tecnologia.")); msg.Add(new ChatMessageContent(AuthorRole.System, "Em breve, você será capaz de consultar o linkedin.")); msg.Add(new ChatMessageContent(AuthorRole.User, "Use sempre portugues do Brasil.")); } diff --git a/Controllers/ChatController.cs b/Controllers/ChatController.cs index 2a16dfc..3f32731 100644 --- a/Controllers/ChatController.cs +++ b/Controllers/ChatController.cs @@ -15,80 +15,62 @@ using ChatApi.Services.Classifier; using Microsoft.AspNetCore.Authorization; using System.Security.Claims; using ChatApi.Models; +using ChatApi.Data; #pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. namespace ChatApi.Controllers { - [Authorize] [ApiController] [Route("[controller]")] + [Authorize] public class ChatController : ControllerBase { private readonly ILogger _logger; private readonly TextFilter _textFilter; private readonly ResponseFactory _responseFactory; private readonly ClassifierPersistence _classifierPersistence; + private readonly UserDataRepository _userDataRepository; + private readonly TextData _textData; public ChatController( ILogger logger, TextFilter textFilter, ResponseFactory responseFactory, - ClassifierPersistence classifierPersistence) + ClassifierPersistence classifierPersistence, + UserDataRepository userDataRepository, + TextData textData) { _logger = logger; _textFilter = textFilter; _responseFactory = responseFactory; _classifierPersistence = classifierPersistence; + _userDataRepository = userDataRepository; + _textData = textData; } - [HttpGet(Name = "Response")] - public async Task Get([FromQuery] ChatRequest chatRequest) + [HttpGet] + [Route("response")] + public async Task GetResponse([FromQuery] ChatRequest chatRequest) { - var userData = UserData.Create(User); + //var userData = UserData.Create(User); var textClassifier = new TextClassifier(_textFilter, _classifierPersistence); var textType = await textClassifier.ClassifyQuestion(chatRequest.SessionId, chatRequest.Message); + var needsRestart = textClassifier.NeedsRestart(); var responseText = _responseFactory.GetService(textType); - var response = await responseText.GetResponse(HttpContext, userData, chatRequest.SessionId, chatRequest.Message); + var userData = await _userDataRepository.GeByToekntAsync(AppDomain.CurrentDomain.GetData("Token") as string); + var response = await responseText.GetResponse(HttpContext, userData, chatRequest.SessionId, chatRequest.Message, needsRestart); return response; } - [HttpPost(Name = "LoadDBData")] - public async Task SaveData([FromQuery] DBLoadRequest loadRequest) + [HttpPost] + [Route("loaddata")] + //public async Task SaveData([FromQuery] DBLoadRequest loadRequest) + public async Task SaveData() { - //string readText = System.IO.File.ReadAllText("C:\\vscode\\ChatApi\\bin\\Debug\\net8.0\\Servicos.txt"); - string readText = loadRequest.Content; - await SalvarTextoComEmbeddingNoMongoDB(readText); - } - - - async Task SalvarTextoComEmbeddingNoMongoDB(string textoCompleto) - { - var textoArray = new List(); - string[] textolinhas = textoCompleto.Split( - new string[] { "\n" }, - StringSplitOptions.None - ); - - var title = textolinhas[0]; - - var builder = new StringBuilder(); - foreach (string line in textolinhas) - { - if (line.StartsWith("**") || line.StartsWith("\r**")) - { - if (builder.Length > 0) - { - textoArray.Add(title.Replace("**", "").Replace("\r", "") + ": " + Environment.NewLine + builder.ToString()); - builder = new StringBuilder(); - title = line; - } - } - else - { - builder.AppendLine(line); - } - } + string readText = System.IO.File.ReadAllText("C:\\vscode\\ChatApi\\bin\\Debug\\net8.0\\Servicos.txt"); + //string readText = loadRequest.Content; + await _textData.SalvarTextoComEmbeddingNoMongoDB(readText); } } } diff --git a/Controllers/ChatController.cs~RF39ccb1.TMP b/Controllers/ChatController.cs~RF39ccb1.TMP deleted file mode 100644 index f682375..0000000 --- a/Controllers/ChatController.cs~RF39ccb1.TMP +++ /dev/null @@ -1,68 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.ChatCompletion; -using Microsoft.SemanticKernel.Embeddings; -using Microsoft.SemanticKernel.Memory; -using Microsoft.SemanticKernel.Connectors.InMemory; - -#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - -namespace ChatApi.Controllers -{ - [ApiController] - [Route("[controller]")] - public class ChatController : ControllerBase - { - private readonly ILogger _logger; - private readonly ChatHistoryService _chatHistoryService; - private readonly IChatCompletionService _chatCompletionService; - private readonly Kernel _kernel; - - public ChatController(ILogger logger, ChatHistoryService chatHistoryService, IChatCompletionService chatCompletionService, Kernel kernel) - { - _logger = logger; - _chatHistoryService = chatHistoryService; - _chatCompletionService = chatCompletionService; - _kernel = kernel; - } - - [HttpGet(Name = "Response")] - public async Task Get([FromQuery] ChatRequest chatRequest) - { - var stopWatch = new System.Diagnostics.Stopwatch(); - - stopWatch.Start(); - SessionIdStore sessionIdStore = new SessionIdStore(HttpContext); - var sessionId = sessionIdStore.GetSessionId(); - var history = _chatHistoryService.Get(sessionId); - - history.AddUserMessage(chatRequest.Message); - - var embeddingGenerator = _kernel.GetRequiredService(); - // Setup a memory store and create a memory out of it -#pragma warning disable SKEXP0020 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var memoryStore = new InMemoryVectorStore(); -#pragma warning restore SKEXP0020 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - var memory = new SemanticTextMemory(memoryStore, embeddingGenerator); - // Loading it for Save, Recall and other methods - _kernel.ImportPluginFromObject(new TextMemoryPlugin(memory)); - string MemoryCollectionName = "MyCustomDataCollection"; - - var option = new PromptExecutionSettings - { - - Memory = memory, - MemoryCollectionName = MemoryCollectionName - }; - - var response = await _chatCompletionService.GetChatMessageContentAsync(history); - history.AddMessage(response.Role, response.Content ?? ""); - - _chatHistoryService.UpdateHistory(sessionId, history); - - stopWatch.Stop(); - return $"{response.Content ?? ""}\n\nTempo: {stopWatch.ElapsedMilliseconds/1000}s"; - } - } -} -#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/Controllers/LoginController.cs b/Controllers/LoginController.cs new file mode 100644 index 0000000..9e75642 --- /dev/null +++ b/Controllers/LoginController.cs @@ -0,0 +1,121 @@ +using ChatApi.Models; +using ChatApi.Services.Crypt; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.IdentityModel.Tokens; +using System.Globalization; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; + +namespace ChatApi.Controllers +{ + [Route("[controller]")] + [ApiController] + public class LoginController : ControllerBase + { + private readonly IConfigurationManager _configuration; + private readonly UserDataRepository _userDataRepository; + private readonly CryptUtil _cryptUtil; + + public LoginController(IConfigurationManager configuration, UserDataRepository userDataRepository, CryptUtil cryptUtil) + { + _configuration = configuration; + _userDataRepository = userDataRepository; + _cryptUtil = cryptUtil; + } + + [AllowAnonymous] + [HttpPost] + [Route("token")] + public async Task Post([FromBody] LoginRequest loginRequest) + { + if (ModelState.IsValid) + { + try + { + var userDataFrom = await _userDataRepository.GetAsync(loginRequest.ClientName, loginRequest.ClientId, loginRequest.ClientSecret); + if (userDataFrom==null) + { + return Unauthorized(); + } + + var token = ""; + if (userDataFrom.LastToken == null && (userDataFrom.DateTimeToken != null && userDataFrom.DateTimeToken.Value.AddHours(24) > DateTime.Now)) + { + token = userDataFrom.LastToken; + } + else + { + var claims = new[] + { + new Claim("Sub", userDataFrom.CompanyTenant), + new Claim("NameId", userDataFrom.Name), + new Claim(ClaimTypes.NameIdentifier, loginRequest.ClientId), + new Claim("DhCriado", DateTime.Now.ToString(new CultureInfo("pt-BR"))), + new Claim("TenantId", userDataFrom.CompanyTenant), + new Claim(ClaimTypes.Role, "TeamsUser") + }; + + var expires = DateTime.UtcNow.AddMinutes(30); + var tokenGen = new JwtSecurityToken + ( + issuer: _configuration["Issuer"], + audience: _configuration["Audience"], + claims: claims, + expires: expires, + notBefore: DateTime.UtcNow, + signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])), + SecurityAlgorithms.HmacSha256) + ); + + token = new JwtSecurityTokenHandler().WriteToken(tokenGen); + } + userDataFrom.LastToken = token; + userDataFrom.DateTimeToken = DateTime.Now; + await _userDataRepository.UpdateAsync(userDataFrom.Id, userDataFrom); + + return Ok(new { token = token }); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } + } + + return BadRequest(); + } + + [AllowAnonymous] + [HttpPost] + [Route("newclient")] + public async Task NewClient([FromBody] UserRequest userDataFrom) + { + if (ModelState.IsValid) + { + try + { + var userData = await _userDataRepository.GetAsync(userDataFrom.Name, userDataFrom.LocalId); + if (userData == null) + { + var secret = _cryptUtil.Encrypt(JsonSerializer.Serialize(userDataFrom)); + userData = UserData.Create(userDataFrom, secret); + await _userDataRepository.CreateAsync(userData); + } + + return Created("newclient", userData); + } + catch (Exception ex) + { + return StatusCode(500, ex.Message); + } + } + + return BadRequest(); + } + } +} diff --git a/Controllers/LoginRequest.cs b/Controllers/LoginRequest.cs new file mode 100644 index 0000000..d32158e --- /dev/null +++ b/Controllers/LoginRequest.cs @@ -0,0 +1,9 @@ +namespace ChatApi.Controllers +{ + public class LoginRequest + { + public string ClientId { get; set; } + public string ClientName { get; set; } + public string ClientSecret { get; set; } + } +} diff --git a/Controllers/UserRequest.cs b/Controllers/UserRequest.cs new file mode 100644 index 0000000..dca2b6d --- /dev/null +++ b/Controllers/UserRequest.cs @@ -0,0 +1,16 @@ +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson; +using System.Text.Json.Serialization; + +namespace ChatApi.Controllers +{ + public class UserRequest + { + public string Name { get; set; } + public string CompanyTenant { get; set; } + public string LocalId { get; set; } + + [JsonIgnore] + public string? Secret { get; set; } + } +} \ No newline at end of file diff --git a/Data/TextData.cs b/Data/TextData.cs new file mode 100644 index 0000000..22d61bb --- /dev/null +++ b/Data/TextData.cs @@ -0,0 +1,73 @@ +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Embeddings; +using System.Text; + +#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + +namespace ChatApi.Data +{ + public class TextData + { + private readonly ITextEmbeddingGenerationService _textEmbeddingGenerationService; + private readonly SharepointDomvsService _sharepointDomvsService; + + public TextData(ITextEmbeddingGenerationService textEmbeddingGenerationService, SharepointDomvsService sharepointDomvsService) + { + _textEmbeddingGenerationService = textEmbeddingGenerationService; + _sharepointDomvsService = sharepointDomvsService; + } + + public async Task SalvarTextoComEmbeddingNoMongoDB(string textoCompleto) + { + var textoArray = new List(); + string[] textolinhas = textoCompleto.Split( + new string[] { "\n" }, + StringSplitOptions.None + ); + + var title = textolinhas[0]; + + var builder = new StringBuilder(); + foreach (string line in textolinhas) + { + if (line.StartsWith("**") || line.StartsWith("\r**")) + { + if (builder.Length > 0) + { + textoArray.Add(title.Replace("**", "").Replace("\r", "") + ": " + Environment.NewLine + builder.ToString()); + builder = new StringBuilder(); + title = line; + } + } + else + { + builder.AppendLine(line); + } + } + + foreach(var item in textoArray) + { + await SalvarNoMongoDB(item); + } + } + + private async Task SalvarNoMongoDB(string texto) + { + // Gerar embedding para o texto + var embedding = await _textEmbeddingGenerationService.GenerateEmbeddingAsync(texto); + + // Converter embedding para um formato serializável (como um array de floats) + var embeddingArray = embedding.ToArray().Select(e => (double)e).ToArray(); + + // Criar documento MongoDB com conteúdo e embedding + var documento = new TextoComEmbedding + { + Conteudo = texto, + Embedding = embeddingArray + }; + + await _sharepointDomvsService.CreateAsync(documento); + } + } +} +#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/Data/UserDataRepository.cs b/Data/UserDataRepository.cs new file mode 100644 index 0000000..70f07e7 --- /dev/null +++ b/Data/UserDataRepository.cs @@ -0,0 +1,45 @@ +using ChatApi.Models; +using Microsoft.Extensions.Options; +using MongoDB.Driver; + +namespace ChatApi +{ + public class UserDataRepository + { + private readonly IMongoCollection _userCollection; + + public UserDataRepository( + IOptions bookStoreDatabaseSettings) + { + var mongoClient = new MongoClient( + bookStoreDatabaseSettings.Value.ConnectionString); + + var mongoDatabase = mongoClient.GetDatabase( + bookStoreDatabaseSettings.Value.DatabaseName); + + _userCollection = mongoDatabase.GetCollection( + bookStoreDatabaseSettings.Value.UserDataName); + } + + public async Task> GetAsync() => + await _userCollection.Find(_ => true).ToListAsync(); + + public async Task GeByToekntAsync(string token) => + await _userCollection.Find(x => x.LastToken == token).FirstOrDefaultAsync(); + public async Task GetAsync(string id) => + await _userCollection.Find(x => x.Id == id).FirstOrDefaultAsync(); + public async Task GetAsync(string name, string localId) => + await _userCollection.Find(x => x.Name == name && x.LocalId == localId).FirstOrDefaultAsync(); + public async Task GetAsync(string name, string localId, string secret) => + await _userCollection.Find(x => x.Name == name && x.LocalId == localId && x.Secret == secret).FirstOrDefaultAsync(); + + public async Task CreateAsync(UserData newBook) => + await _userCollection.InsertOneAsync(newBook); + + public async Task UpdateAsync(string id, UserData updatedBook) => + await _userCollection.ReplaceOneAsync(x => x.Id == id, updatedBook); + + public async Task RemoveAsync(string id) => + await _userCollection.DeleteOneAsync(x => x.Id == id); + } +} diff --git a/DomvsDatabaseSettings.cs b/DomvsDatabaseSettings.cs index eeb97bd..5c310e1 100644 --- a/DomvsDatabaseSettings.cs +++ b/DomvsDatabaseSettings.cs @@ -8,6 +8,8 @@ public string SharepointCollectionName { get; set; } = null!; + public string UserDataName { get; set; } = null!; + public string ChatBotRHCollectionName { get; set; } = null!; public string ClassifierCollectionName { get; set; } = null!; diff --git a/Models/UserData.cs b/Models/UserData.cs index a1e9052..571e444 100644 --- a/Models/UserData.cs +++ b/Models/UserData.cs @@ -1,29 +1,37 @@ -using System.Security.Claims; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson; +using System.Security.Claims; +using ChatApi.Controllers; namespace ChatApi.Models { public class UserData { - private UserData() + public UserData() { } - public string Email { get; set; } + [BsonId] + [BsonElement("_id")] + [BsonRepresentation(BsonType.String)] + public string Id { get; set; } + public string LocalId { get; set; } public string Name { get; set; } + public string Secret { get; set; } + public string CompanyTenant { get; set; } + public string? Email { get; set; } + public string? LastToken { get; set; } + public DateTime? DateTimeToken { get; set; } - public static UserData Create(ClaimsPrincipal user) + public static UserData Create(UserRequest userRequest, string secret) { - var email = user.FindFirst(ClaimTypes.Email)?.Value - ?? user.FindFirst("email")?.Value; - - var name = user.FindFirst(ClaimTypes.Name)?.Value - ?? user.FindFirst("name")?.Value; - - return new UserData { - Email = email, - Name = name + Id = Guid.NewGuid().ToString("N"), + Name = userRequest.Name, + CompanyTenant = userRequest.CompanyTenant, + LocalId = userRequest.LocalId, + Secret = secret }; } } diff --git a/Program.cs b/Program.cs index 52e4030..dd3a41e 100644 --- a/Program.cs +++ b/Program.cs @@ -1,15 +1,25 @@ using ChatApi; +using ChatApi.Data; using ChatApi.Services; using ChatApi.Services.Bot; using ChatApi.Services.Bot.Structs; using ChatApi.Services.Classifier; +using ChatApi.Services.Crypt; using ChatApi.Services.ResponseService; using ChatApi.Settings; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using OllamaSharp; +using System.IdentityModel.Tokens.Jwt; +using System.Reflection.Metadata; +using System.Text; +using static OllamaSharp.OllamaApiClient; +using static System.Net.Mime.MediaTypeNames; #pragma warning disable SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. @@ -20,7 +30,34 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "apichat", Version = "v1" }); + + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() + { + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer'[space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"", + }); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] {} + } + }); +}); builder.Services.Configure( builder.Configuration.GetSection("DomvsDatabase")); @@ -38,6 +75,9 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddTransient(); +builder.Services.AddTransient(); +builder.Services.AddSingleton(); //builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); @@ -63,6 +103,7 @@ builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http:// //); builder.Services.AddKernel(); + //builder.Services.AddKernel() // .AddOllamaChatCompletion("phi3", new Uri("http://localhost:11435")) // .AddOllamaTextEmbeddingGeneration() @@ -74,22 +115,34 @@ builder.Services.AddHttpClient(); var tenantId = builder.Configuration.GetSection("AppTenantId"); var clientId = builder.Configuration.GetSection("AppClientID"); -builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => - { - options.Authority = $"https://login.microsoftonline.com/{tenantId}/v2.0"; - options.Audience = "api://" + clientId; // Client ID da sua API - options.TokenValidationParameters = new TokenValidationParameters +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) +.AddJwtBearer(jwtBearerOptions => +{ + jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters() + { + ValidateActor = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = builder.Configuration["Issuer"], + ValidAudience = builder.Configuration["Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["SigningKey"])) + }; + jwtBearerOptions.Events = new JwtBearerEvents() + { + OnTokenValidated = async context => { - ValidateIssuer = true, - ValidateAudience = true, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuer = $"https://login.microsoftonline.com/{tenantId}/v2.0", - ValidAudience = "api://" + clientId - }; - }); + var token = context.SecurityToken as JsonWebToken; + context.HttpContext.Items["Token"] = token.EncodedToken; + AppDomain.CurrentDomain.SetData("Token", token.EncodedToken); + } + }; +}) +; + +//builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) +// .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); builder.Services.AddControllers(); @@ -119,6 +172,7 @@ builder.Services.AddControllers(); // }; // }); +builder.Services.AddSingleton(builder.Configuration); var app = builder.Build(); @@ -131,8 +185,6 @@ if (app.Environment.IsDevelopment()) app.UseHttpsRedirection(); -app.UseAuthorization(); - app.MapControllers(); app.Use(async (context, next) => diff --git a/Services/Bot/ActionCreateCall.cs b/Services/Bot/ActionCreateCall.cs index 001287f..865460c 100644 --- a/Services/Bot/ActionCreateCall.cs +++ b/Services/Bot/ActionCreateCall.cs @@ -51,10 +51,10 @@ namespace ChatApi.Services.Bot _callRH = new { Nome = knowParameters["Nome"], - Email = knowParameters["Email"], - WhatsApp = answers[0], - TipoSolicitacao = answers[1], - Descricao = answers[2], + Email = answers[2], + WhatsApp = answers[1], + TipoSolicitacao = answers[3], + Descricao = answers[4], }; return Task.FromResult(Result.Ok()); diff --git a/Services/Bot/ChatBotRHCall.cs b/Services/Bot/ChatBotRHCall.cs index b4adb1a..4b51ae0 100644 --- a/Services/Bot/ChatBotRHCall.cs +++ b/Services/Bot/ChatBotRHCall.cs @@ -1,5 +1,6 @@ using ChatApi.Models; using ChatApi.Services.Bot.Structs; +using ChatApi.Services.Emails; using ChatApi.Settings; using Microsoft.Extensions.Options; using Microsoft.SemanticKernel.ChatCompletion; @@ -51,7 +52,7 @@ namespace ChatApi.Services.Bot _chatRHApiConfig = chatRHApiConfig; _config = config; } - public string SetAnswer(string sessionId, string resposta) + public string SetAnswer(string sessionId, string resposta, bool needsRestart = false) { if (_chatbot==null) { @@ -61,7 +62,7 @@ namespace ChatApi.Services.Bot return _chatbot.SetAnswer(resposta); } - public async Task GetNextQuestion() + public async Task GetNextQuestion() { return await _chatbot.GetNextQuestion(); } @@ -76,6 +77,11 @@ namespace ChatApi.Services.Bot return _chatbot.HasNextQuestion(); } + public async Task CallFinalAction() + { + return await _chatbot.CallFinalAction(); + } + public void Init(string sessionId) { var persist = ChatPersistence.Create(_config.Value).SetBotName("RHCall"); @@ -92,20 +98,27 @@ namespace ChatApi.Services.Bot "Qual seu número de celular (com WhatsApp)?", resposta => !string.IsNullOrEmpty(resposta) && resposta.Length >= 10)); + _chatbot.AddQuestion(new Question( + "Qual seu e-mail?", + resposta => EmailValidate.IsValidEmail(resposta))); + var builder = new StringBuilder(); _opcoesSolicitação.Select(s => s.Value).ToList().ForEach(s => builder.AppendLine(s)); + var validNumbers = _opcoesSolicitação.Select(s => s.Value.Substring(0, s.Value.IndexOf("-")-1)).ToList(); _chatbot.AddQuestion(new Question( $"Indique o tipo de solicitação: \n {builder}", - resposta => resposta == "1" || resposta == "2" || resposta == "3")); + resposta => validNumbers.IndexOf(resposta) != -1, + null, + text => _opcoesSolicitação.FirstOrDefault(s => s.Value.Substring(0, s.Value.IndexOf("-")-1) == text).Key)); _chatbot.AddQuestion(new Question( "Texto/Descrição da solicitação (em caso de dúvidas)", resposta => !string.IsNullOrEmpty(resposta))); _chatbot.AddQuestion(new Question( - "Tudo bem? Posso enviar sua solicitação? Ou prefere que eu tente fazer algum ajuste no texto da descrição?", - resposta => !string.IsNullOrEmpty(resposta))); + "Tudo bem? Posso enviar sua solicitação? Ou prefere que eu tente fazer algum ajuste no texto da descrição? (SIM ou NÃO)", + resposta => resposta.ToUpper() == "SIM" || resposta.ToUpper() == "NÃO")); } private void AddNumbersBeforShow(Dictionary dict) { diff --git a/Services/Bot/Structs/4c0w1xuj.h1l~ b/Services/Bot/Structs/4c0w1xuj.h1l~ new file mode 100644 index 0000000..1c3473f --- /dev/null +++ b/Services/Bot/Structs/4c0w1xuj.h1l~ @@ -0,0 +1,171 @@ +using Amazon.Auth.AccessControlPolicy; +using ChatApi.Models; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace ChatApi.Services.Bot.Structs +{ + public class ChatBot + { + private readonly List _questions; + private readonly ChatPersistence _persistence; + private readonly Dictionary _answers; + private readonly IChatCompletionService _chatCompletionService; + private readonly IActionCall _actionCall; + private readonly string _usuarioId; + private readonly UserData _userData; + private Dictionary _knowParameters; + private int _indiceAtual; + + public ChatBot(ChatPersistence persistence, string usuarioId, UserData userData, IChatCompletionService chatCompletionService, IActionCall actionCall) + { + _chatCompletionService = chatCompletionService; + _actionCall = actionCall; + _questions = new List(); + _answers = new Dictionary(); + _knowParameters = new Dictionary(); + _indiceAtual = 0; + _persistence = persistence; + _usuarioId = usuarioId; + _userData = userData; + var estado = _persistence.GetState(_usuarioId); + if (estado != null) + { + _indiceAtual = estado.IndicePerguntaAtual; + _answers = estado.Respostas; + } + else + { + _indiceAtual = 0; + } + } + + public void SetParameter(string key, string value) + { + _knowParameters[key] = value; + } + + public void AddQuestion(Question Question) + { + _questions.Add(Question); + } + + public string SetAnswer(string resposta) + { + if (!this.HasNextQuestion()) return ""; + + var state = _persistence.GetState(_usuarioId); + if (state != null) + { + _questions[_indiceAtual].CountTry = state.TentativasPerguntaAtual; + } + else + { + _knowParameters = new Dictionary() + { + { "Nome", _userData.Name } + }; + } + + var perguntaAtual = _questions[_indiceAtual]; + var testByChat = TestAnswerByChat(resposta); + var abort = TestIfWantToAbort(resposta); + var validResp = perguntaAtual.TryToAnswer(resposta); + + if (string.IsNullOrEmpty(validResp) && !abort.Contains("ABORTAR") && perguntaAtual.Validator(resposta) && testByChat.Contains("SIM")) + { + _answers[_indiceAtual] = perguntaAtual.ConvertToSave == null ? resposta : perguntaAtual.ConvertToSave(resposta); + _indiceAtual++; + + if (_indiceAtual < _questions.Count()) + { + _persistence.SaveState(new ChatState + { + Id = _usuarioId, + UsuarioId = _usuarioId, + IndicePerguntaAtual = _indiceAtual, + TentativasPerguntaAtual = _questions[_indiceAtual].CountTry, + Respostas = new Dictionary(_answers) + }); + } + + return ""; + } + else if (validResp.Contains("reiniciar") || abort.Contains("ABORTAR")) + { + _persistence.DeleteState(_usuarioId); + return "REINICIAR"; + } + + _persistence.SaveState(new ChatState + { + Id = _usuarioId, + UsuarioId = _usuarioId, + IndicePerguntaAtual = _indiceAtual, + TentativasPerguntaAtual = _questions[_indiceAtual].CountTry, + DadosConhecidos = _knowParameters, + Respostas = new Dictionary(_answers) + }); + + return $"Resposta inválida. Tente novamente. {(testByChat.Contains("NÃO") ? "Motivo: " + testByChat.Replace("NÂO", "") : "")} \n\n {perguntaAtual.Text}"; + } + + public string GetCurrentQuestion() => _questions[_indiceAtual].Text; + public async Task GetNextQuestion() + { + while (_indiceAtual < _questions.Count) + { + var perguntaAtual = _questions[_indiceAtual]; + if (perguntaAtual.MustShow(_answers)) + { + return perguntaAtual.Text; + } + _indiceAtual++; + } + + //_knowParameters = new Dictionary() + // { + // { "Nome", _userData.Name } + // }; + + //var resp = await _actionCall.Populate(_knowParameters, _answers); + //if (!resp.Success) return "Obrigado por responder a nossas perguntas!"; + + //var result = await _actionCall.Call(); + + //return result.Success ? result.Value : "Obrigado por responder a nossas perguntas!"; + } + + public async Task CallFinalAction() + { + _knowParameters = new Dictionary() + { + { "Nome", _userData.Name } + }; + + var resp = await _actionCall.Populate(_knowParameters, _answers); + if (!resp.Success) return "Obrigado por responder a nossas perguntas!"; + + var result = await _actionCall.Call(); + + return result.Success ? result.Value : "Obrigado por responder a nossas perguntas!"; + } + + public string TestAnswerByChat(string resposta) + { + var resp = _chatCompletionService.GetChatMessageContentAsync($"Por favor, responda com SIM/NÂO e o motivo para eu saber se a pergunta: ({this.GetCurrentQuestion()}) foi respondida pelo usuário quando ele digitou: ({resposta})? É um chatbot, por isso preciso saber apenas se a resposta faz sentido e/ou tem o formato certo.").Result; + return resp.Content; + } + + public string TestIfWantToAbort(string resposta) + { + if (_indiceAtual==0) return ""; + var resp = _chatCompletionService.GetChatMessageContentAsync($"Este é um chatbot. Foi feita a pergunta para o usuario: ({this.GetCurrentQuestion()}) e ele digitou: ({resposta})? Ele respondeu a pergunta com a informação solicitada, ou ele está pedindo para sair do chat? Responda com (SIM = Ele quer sair ou não quer responder) ou (NÃO = Ele responde a pergunta de maneira coerente) e indique o motivo.").Result; + return resp.Content.Contains("SIM") ? "ABORTAR" : ""; + } + + public bool HasNextQuestion() + { + return _indiceAtual < _questions.Count; + } + } +} diff --git a/Services/Bot/Structs/ChatBot.cs b/Services/Bot/Structs/ChatBot.cs index 7885d1f..51a8b21 100644 --- a/Services/Bot/Structs/ChatBot.cs +++ b/Services/Bot/Structs/ChatBot.cs @@ -1,6 +1,7 @@ using Amazon.Auth.AccessControlPolicy; using ChatApi.Models; using Microsoft.SemanticKernel.ChatCompletion; +using System.Text; namespace ChatApi.Services.Bot.Structs { @@ -49,11 +50,27 @@ namespace ChatApi.Services.Bot.Structs _questions.Add(Question); } - public string SetAnswer(string resposta) + public string SetAnswer(string resposta, bool needsRestart = false) { - if (!this.HasNextQuestion()) return ""; + var stops = new List() + { + "ABORTAR", + "ABORTE", + "SAIR", + "SAIA", + "REINICIAR", + "REINICIE" + }; + + if (!this.HasNextQuestion()) { + SetQuestionIndex(_usuarioId, true); + return ""; + } var state = _persistence.GetState(_usuarioId); + + SetQuestionIndex(_usuarioId, needsRestart); + if (state != null) { _questions[_indiceAtual].CountTry = state.TentativasPerguntaAtual; @@ -62,28 +79,42 @@ namespace ChatApi.Services.Bot.Structs { _knowParameters = new Dictionary() { - { "Nome", _userData.Name }, - { "Email", _userData.Email } + { "Nome", _userData.Name } }; } + //var testAll = TestAllQuestions(resposta); + + //if (int.TryParse(testAll, out int index)) + // _indiceAtual = index < 0 ? _indiceAtual : index - 1; + + //var abort = resposta.ToUpper(); + //if (index < 0) abort= "ABORTAR"; + + //var perguntaAtual = _questions[_indiceAtual]; + //var testByChat = TestAnswerByChat(resposta); + var perguntaAtual = _questions[_indiceAtual]; - var testByChat = TestAnswerByChat(resposta); - var abort = TestIfWantToAbort(resposta); + var abort = resposta.ToUpper(); var validResp = perguntaAtual.TryToAnswer(resposta); - if (string.IsNullOrEmpty(validResp) && !abort.Contains("ABORTAR") && perguntaAtual.Validator(resposta) && testByChat.Contains("SIM")) + //if (string.IsNullOrEmpty(validResp) && !abort.Contains("ABORTAR") && perguntaAtual.Validator(resposta) && testByChat.Contains("SIM")) + if (string.IsNullOrEmpty(validResp) && !stops.Contains(abort)) { - _answers[_indiceAtual] = resposta; + _answers[_indiceAtual] = perguntaAtual.ConvertToSave == null ? resposta : perguntaAtual.ConvertToSave(resposta); _indiceAtual++; - _persistence.SaveState(new ChatState + + if (_indiceAtual < _questions.Count()) { - Id = _usuarioId, - UsuarioId = _usuarioId, - IndicePerguntaAtual = _indiceAtual, - TentativasPerguntaAtual = _questions[_indiceAtual].CountTry, - Respostas = new Dictionary(_answers) - }); + _persistence.SaveState(new ChatState + { + Id = _usuarioId, + UsuarioId = _usuarioId, + IndicePerguntaAtual = _indiceAtual, + TentativasPerguntaAtual = _questions[_indiceAtual].CountTry, + Respostas = new Dictionary(_answers) + }); + } return ""; } @@ -103,7 +134,8 @@ namespace ChatApi.Services.Bot.Structs Respostas = new Dictionary(_answers) }); - return $"Resposta inválida. Tente novamente. {(testByChat.Contains("NÃO") ? "Motivo: " + testByChat.Replace("NÂO", "") : "")} \n\n {perguntaAtual.Text}"; + //return $"Resposta inválida. Tente novamente. {(testByChat.Contains("NÃO") ? "Motivo: " + testByChat.Replace("NÂO", "") : "")} \n\n {perguntaAtual.Text}"; + return $"Resposta inválida. Tente novamente. \n{perguntaAtual.Text} \nSua resposta: {resposta}"; } public string GetCurrentQuestion() => _questions[_indiceAtual].Text; @@ -119,6 +151,16 @@ namespace ChatApi.Services.Bot.Structs _indiceAtual++; } + return null; + } + + public async Task CallFinalAction() + { + _knowParameters = new Dictionary() + { + { "Nome", _userData.Name } + }; + var resp = await _actionCall.Populate(_knowParameters, _answers); if (!resp.Success) return "Obrigado por responder a nossas perguntas!"; @@ -135,14 +177,42 @@ namespace ChatApi.Services.Bot.Structs public string TestIfWantToAbort(string resposta) { - if (_indiceAtual==0) return ""; + if (_indiceAtual == 0) return ""; var resp = _chatCompletionService.GetChatMessageContentAsync($"Este é um chatbot. Foi feita a pergunta para o usuario: ({this.GetCurrentQuestion()}) e ele digitou: ({resposta})? Ele respondeu a pergunta com a informação solicitada, ou ele está pedindo para sair do chat? Responda com (SIM = Ele quer sair ou não quer responder) ou (NÃO = Ele responde a pergunta de maneira coerente) e indique o motivo.").Result; return resp.Content.Contains("SIM") ? "ABORTAR" : ""; } + public string? TestAllQuestions(string resposta) + { + if (_indiceAtual == 0) return ""; + + var builder = new StringBuilder(); + for (int i = 0; i < _questions.Count; i++) + { + builder.AppendLine($"{i + 1} - {_questions[i].Text}"); + } + + var resp = _chatCompletionService.GetChatMessageContentAsync($"Este é um chatbot. Você pode identificar se a resposta do usuario: \"{resposta}\" tem a inteção de responder uma das perguntas: { builder.ToString() } ? Por favor, reponda com o número da pergunta que o usuário tentou responder, ou com a palavra -1 se não identificou, ou por ultimo com -2 se identificar que o usuário não quis responder nenhuma das perguntas.").Result; + return resp.Content; + } + public bool HasNextQuestion() { return _indiceAtual < _questions.Count; } + + private void SetQuestionIndex(string usuarioId, bool needsRestart) + { + var estado = _persistence.GetState(usuarioId); + if (estado != null && !needsRestart) + { + _indiceAtual = estado.IndicePerguntaAtual; + } + else + { + _indiceAtual = 0; + } + } + } } diff --git a/Services/Bot/Structs/Question.cs b/Services/Bot/Structs/Question.cs index 4156464..e12eb74 100644 --- a/Services/Bot/Structs/Question.cs +++ b/Services/Bot/Structs/Question.cs @@ -6,15 +6,17 @@ public Func Validator { get; } public int CountTry { get; set; } public Func, bool> Condition { get; } + public Func ConvertToSave { get; } private const int MaxTentativas = 2; - public Question(string texto, Func validador, Func, bool> condition=null) + public Question(string texto, Func validador, Func, bool> condition=null, Func convertToSave = null) { Text = texto; Validator = validador; CountTry = 0; Condition = condition; + ConvertToSave = convertToSave; } public bool MustShow(Dictionary respostasAnteriores) diff --git a/Services/Classifier/ClassifierSate.cs b/Services/Classifier/ClassifierSate.cs index 1968dda..520ef50 100644 --- a/Services/Classifier/ClassifierSate.cs +++ b/Services/Classifier/ClassifierSate.cs @@ -13,5 +13,6 @@ namespace ChatApi.Services.Classifier public EnumClassification Classification { get; set; } public DateTime DhInicio { get; set; } public EnumClassificationType ClassificationType { get; set; } + public EnumClassification LastClassification { get; set; } } } diff --git a/Services/Crypt/CryptUtil.cs b/Services/Crypt/CryptUtil.cs new file mode 100644 index 0000000..31cc133 --- /dev/null +++ b/Services/Crypt/CryptUtil.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace ChatApi.Services.Crypt +{ + public class CryptUtil + { + private readonly IConfigurationManager _configuration; + + public CryptUtil(IConfigurationManager configuration) + { + _configuration = configuration; + } + public string Encrypt(string text) + { + string key = _configuration.GetSection("DataKey").Value; + using (Aes aes = Aes.Create()) + { + aes.Key = Encoding.UTF8.GetBytes(key.PadRight(32).Substring(0, 32)); + aes.GenerateIV(); // Cria um vetor de inicialização aleatório + byte[] iv = aes.IV; + + using (var encryptor = aes.CreateEncryptor(aes.Key, iv)) + using (var ms = new MemoryStream()) + { + // Primeiro, escreva o IV para o fluxo (será necessário para descriptografia) + ms.Write(iv, 0, iv.Length); + + using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + using (var writer = new StreamWriter(cs)) + { + writer.Write(text); + } + + return Convert.ToBase64String(ms.ToArray()); + } + } + } + + public string Descrypt(string text) + { + string key = _configuration.GetSection("DataKey").Value; + byte[] bytes = Convert.FromBase64String(text); + + using (Aes aes = Aes.Create()) + { + aes.Key = Encoding.UTF8.GetBytes(key.PadRight(32).Substring(0, 32)); + + // Extrair o IV do início dos dados criptografados + byte[] iv = new byte[aes.BlockSize / 8]; + Array.Copy(bytes, 0, iv, 0, iv.Length); + aes.IV = iv; + + using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) + using (var ms = new MemoryStream(bytes, iv.Length, bytes.Length - iv.Length)) + using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) + using (var reader = new StreamReader(cs)) + { + return reader.ReadToEnd(); + } + } + } + } +} diff --git a/Services/Emails/EmailValidate.cs b/Services/Emails/EmailValidate.cs new file mode 100644 index 0000000..39cfc6c --- /dev/null +++ b/Services/Emails/EmailValidate.cs @@ -0,0 +1,24 @@ +namespace ChatApi.Services.Emails +{ + public class EmailValidate + { + public static bool IsValidEmail(string email) + { + var trimmedEmail = email.Trim(); + + if (trimmedEmail.EndsWith(".")) + { + return false; // suggested by @TK-421 + } + try + { + var addr = new System.Net.Mail.MailAddress(email); + return addr.Address == trimmedEmail; + } + catch + { + return false; + } + } + } +} diff --git a/Services/ResponseService/IResponseService.cs b/Services/ResponseService/IResponseService.cs index bbc4fcd..7deaf1e 100644 --- a/Services/ResponseService/IResponseService.cs +++ b/Services/ResponseService/IResponseService.cs @@ -5,6 +5,6 @@ namespace ChatApi.Services.ResponseService public interface IResponseService { EnumClassification Classification { get; } - Task GetResponse(HttpContext context, UserData userData, string sessionId, string question); + Task GetResponse(HttpContext context, UserData userData, string sessionId, string question, bool needsRestart = false); } } diff --git a/Services/ResponseService/ResponseBotRHService.cs b/Services/ResponseService/ResponseBotRHService.cs index fbaeb24..5af20e5 100644 --- a/Services/ResponseService/ResponseBotRHService.cs +++ b/Services/ResponseService/ResponseBotRHService.cs @@ -32,42 +32,61 @@ namespace ChatApi.Services.ResponseService } public EnumClassification Classification => EnumClassification.BotRHCall; - public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question) + public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question, bool needsRestart = false) { var stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); - SessionIdStore sessionIdStore = new SessionIdStore(context); ChatHistory history = _chatHistoryService.GetSumarizer(sessionId); _chatBotCall.UserData = userData; - var resp = _chatBotCall.SetAnswer(sessionId, question); + var resp = _chatBotCall.SetAnswer(sessionId, question, needsRestart); var resposta = ""; - if (string.IsNullOrEmpty(resp) && resp!="REINICIAR") + + try { - resposta = await _chatBotCall.GetNextQuestion(); - history.AddUserMessage(question); - - history.AddMessage(AuthorRole.Assistant, resposta ?? ""); - - _chatHistoryService.UpdateHistory(sessionId, history); - - if (!_chatBotCall.HasNextQuestion()) + if (string.IsNullOrEmpty(resp) && resp != "REINICIAR" && ( question.ToUpper() != "REINICIAR" && question.ToUpper() != "SAIR")) { - _classifierPersistence.DeleteState(sessionId); + resposta = await _chatBotCall.GetNextQuestion(); + + history.AddUserMessage(question); + history.AddMessage(AuthorRole.Assistant, resposta ?? question); + + _chatHistoryService.UpdateHistory(sessionId, history); + + if (!_chatBotCall.HasNextQuestion()) + { + resposta = await _chatBotCall.CallFinalAction(); + _classifierPersistence.DeleteState(sessionId); + _chatBotCall.SetAnswer(sessionId, "REINICIAR", true); + } + } + else + { + if (!needsRestart && resp == "REINICIAR" || (question.ToUpper() != "REINICIAR" || question.ToUpper() != "SAIR")) + { + _classifierPersistence.DeleteState(sessionId); + _chatBotCall.SetAnswer(sessionId, "REINICIAR", true); + resp = "Ok! Parece que você não quer continuar e/ou eu não entendi sua resposta. Tudo bem! Se quiser, tente novamente depois..."; + } + else + { + resp = "Quero abrir um anova solicitação ao RH."; + await GetResponse(context, userData, sessionId, resp, true); + } + + resposta = resp; } } - else + catch (Exception ex) { - if (resp == "REINICIAR") - { - _classifierPersistence.DeleteState(sessionId); - resp = "Ok! Parece que você não quer continuar e/ou eu não entendi sua resposta. Tudo bem! Se quiser, tente novamente depois..."; - } - - resposta = resp; + resposta = "Tivemos um problema. Teremos que reiniciar as perguntas se vc disser que precisa de uma solicitação de rh novamente. Desculpe!"; + _classifierPersistence.DeleteState(sessionId); + } + finally + { + stopWatch.Stop(); } - stopWatch.Stop(); return $"{resposta ?? ""}\n\nTempo: {stopWatch.ElapsedMilliseconds / 1000}s"; } } diff --git a/Services/ResponseService/ResponseChatService.cs b/Services/ResponseService/ResponseChatService.cs index 59f9b27..e2d9669 100644 --- a/Services/ResponseService/ResponseChatService.cs +++ b/Services/ResponseService/ResponseChatService.cs @@ -20,7 +20,7 @@ namespace ChatApi.Services.ResponseService } public EnumClassification Classification => EnumClassification.Chat; - public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question) + public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question, bool needsRestart = false) { var stopWatch = new System.Diagnostics.Stopwatch(); diff --git a/Services/ResponseService/ResponseCompanyService.cs b/Services/ResponseService/ResponseCompanyService.cs index db166ab..ab38536 100644 --- a/Services/ResponseService/ResponseCompanyService.cs +++ b/Services/ResponseService/ResponseCompanyService.cs @@ -32,7 +32,7 @@ namespace ChatApi.Services.ResponseService } public EnumClassification Classification => EnumClassification.Company; - public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question) + public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question, bool needsRestart = false) { var stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); diff --git a/Services/TextClassifier.cs b/Services/TextClassifier.cs index 0f7a925..dc61aae 100644 --- a/Services/TextClassifier.cs +++ b/Services/TextClassifier.cs @@ -19,6 +19,11 @@ namespace ChatApi.Services { private readonly TextFilter _textFilter; private readonly ClassifierPersistence _classifierPersistence; + private bool _needsRestart = false; + public bool NeedsRestart() + { + return _needsRestart; + } public TextClassifier(TextFilter textFilter, ClassifierPersistence classifierPersistence) { @@ -45,11 +50,14 @@ namespace ChatApi.Services var classify = classifyCompany.Handle(question); + _needsRestart = state != null && classify == EnumClassification.BotRHCall && state.Classification != classify; + _classifierPersistence.SaveState(new ClassifierSate { Id = sessionId, UsuarioId = sessionId, Classification = classify, + LastClassification = state != null ? state.Classification : classify, DhInicio = DateTime.Now, ClassificationType = botChat.MyClassification == classify ? EnumClassificationType.Stay : EnumClassificationType.Free }); diff --git a/appsettings.json b/appsettings.json index 4c39c1b..b0af89b 100644 --- a/appsettings.json +++ b/appsettings.json @@ -4,7 +4,8 @@ "DatabaseName": "DomvsSites", "SharepointCollectionName": "SharepointSite", "ChatBotRHCollectionName": "ChatBotRHData", - "ClassifierCollectionName": "ClassifierData" + "ClassifierCollectionName": "ClassifierData", + "UserDataName": "UserData" }, "ChatRHSettings": { "Url": "mongodb://localhost:27017", @@ -18,5 +19,16 @@ }, "AllowedHosts": "*", "AppTenantId": "20190830-5fd4-4a72-b8fd-1c1cb35b25bc", - "AppClientID": "8f4248fc-ee30-4f54-8793-66edcca3fd20" + "AppClientID": "8f4248fc-ee30-4f54-8793-66edcca3fd20", + + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "Domain": "domvsitbr.onmicrosoft.com", + "TenantId": "20190830-5fd4-4a72-b8fd-1c1cb35b25bc", + "ClientId": "8f4248fc-ee30-4f54-8793-66edcca3fd20" + }, + "Issuer": "domvsit.com.br", + "Audience": "domvsit.com.br", + "SigningKey": "D57Ls16KxMPdF4P7qTQtV29slWjJqIJZ", + "DataKey": "NOxGacYtZRJTYCPdSQM75HVSNp3qfH05mPalaE/pL4A6FwxWKQiBhLxhu++LrKsI" }