From bb3315625d9b0ffe4b6bd7d445864e4df5ab2264 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Thu, 28 Nov 2024 22:49:03 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20primeira=20vers=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 30 ++++ .editorconfig | 4 + AuthMiddleware.cs | 11 ++ ChatApi.csproj | 31 ++++ ChatApi.csproj.user | 9 + ChatApi.http | 6 + ChatApi.sln | 25 +++ ChatHistoryService.cs | 92 ++++++++++ ChatHistoryStore.cs | 7 + ChatRequest.cs | 7 + Controllers/ChatController.cs | 95 +++++++++++ Controllers/ChatController.cs~RF39ccb1.TMP | 68 ++++++++ DBLoadRequest.cs | 7 + Dockerfile | 25 +++ DomvsDatabaseSettings.cs | 15 ++ Infra/Result.cs | 56 +++++++ Models/UserData.cs | 30 ++++ Models/UserData.cs~RF3dab6a.TMP | 30 ++++ Program.cs | 157 ++++++++++++++++++ Program.cs~RF419917.TMP | 61 +++++++ Program.cs~RF68750a0.TMP | 98 +++++++++++ Properties/launchSettings.json | 52 ++++++ Services/Bot/5vb0vxog.ejb~ | 64 +++++++ Services/Bot/ActionCreateCall.cs | 63 +++++++ Services/Bot/ChatBotRHCall.cs | 120 +++++++++++++ Services/Bot/Structs/ChatBot.cs | 148 +++++++++++++++++ Services/Bot/Structs/ChatBot.cs~RFf86c93a.TMP | 54 ++++++ Services/Bot/Structs/ChatBot.cs~RFf89467e.TMP | 75 +++++++++ Services/Bot/Structs/ChatPersistence.cs | 45 +++++ Services/Bot/Structs/ChatState.cs | 20 +++ Services/Bot/Structs/IActionCall.cs | 10 ++ Services/Bot/Structs/Question.cs | 42 +++++ Services/Bot/Structs/e4zbjmgl.vwg~ | 137 +++++++++++++++ Services/Bot/Structs/v0pubwmg.zkp~ | 148 +++++++++++++++++ Services/Bot/tdpgs1ms.1ky~ | 64 +++++++ Services/Classifier/ClassifierPersistence.cs | 35 ++++ Services/Classifier/ClassifierSate.cs | 17 ++ .../ClassifierSate.cs~RF14991194.TMP | 6 + Services/ClassifyHandlers/AClassifyHandler.cs | 50 ++++++ .../ClassifyHandlers/ClassifyBotRHCall.cs | 30 ++++ Services/ClassifyHandlers/ClassifyChat.cs | 10 ++ Services/ClassifyHandlers/ClassifyCompany.cs | 25 +++ Services/ClassifyHandlers/ClassifyEstimate.cs | 20 +++ Services/ClassifyHandlers/IHandlerClassify.cs | 11 ++ Services/ResponseService/IResponseService.cs | 10 ++ .../ResponseService/ResponseBotRHService.cs | 76 +++++++++ .../ResponseService/ResponseChatService.cs | 42 +++++ .../ResponseService/ResponseCompanyService.cs | 99 +++++++++++ Services/ResponseService/ResponseFactory.cs | 18 ++ Services/TextClassifier.cs | 60 +++++++ Services/TextFilter.cs | 33 ++++ SessionIdStore.cs | 32 ++++ Settings/ChatRHSettings.cs | 8 + SharepointDomvsService.cs | 38 +++++ TextoComEmbedding.cs | 16 ++ appsettings.Development.json | 19 +++ appsettings.json | 22 +++ 57 files changed, 2583 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 AuthMiddleware.cs create mode 100644 ChatApi.csproj create mode 100644 ChatApi.csproj.user create mode 100644 ChatApi.http create mode 100644 ChatApi.sln create mode 100644 ChatHistoryService.cs create mode 100644 ChatHistoryStore.cs create mode 100644 ChatRequest.cs create mode 100644 Controllers/ChatController.cs create mode 100644 Controllers/ChatController.cs~RF39ccb1.TMP create mode 100644 DBLoadRequest.cs create mode 100644 Dockerfile create mode 100644 DomvsDatabaseSettings.cs create mode 100644 Infra/Result.cs create mode 100644 Models/UserData.cs create mode 100644 Models/UserData.cs~RF3dab6a.TMP create mode 100644 Program.cs create mode 100644 Program.cs~RF419917.TMP create mode 100644 Program.cs~RF68750a0.TMP create mode 100644 Properties/launchSettings.json create mode 100644 Services/Bot/5vb0vxog.ejb~ create mode 100644 Services/Bot/ActionCreateCall.cs create mode 100644 Services/Bot/ChatBotRHCall.cs create mode 100644 Services/Bot/Structs/ChatBot.cs create mode 100644 Services/Bot/Structs/ChatBot.cs~RFf86c93a.TMP create mode 100644 Services/Bot/Structs/ChatBot.cs~RFf89467e.TMP create mode 100644 Services/Bot/Structs/ChatPersistence.cs create mode 100644 Services/Bot/Structs/ChatState.cs create mode 100644 Services/Bot/Structs/IActionCall.cs create mode 100644 Services/Bot/Structs/Question.cs create mode 100644 Services/Bot/Structs/e4zbjmgl.vwg~ create mode 100644 Services/Bot/Structs/v0pubwmg.zkp~ create mode 100644 Services/Bot/tdpgs1ms.1ky~ create mode 100644 Services/Classifier/ClassifierPersistence.cs create mode 100644 Services/Classifier/ClassifierSate.cs create mode 100644 Services/Classifier/ClassifierSate.cs~RF14991194.TMP create mode 100644 Services/ClassifyHandlers/AClassifyHandler.cs create mode 100644 Services/ClassifyHandlers/ClassifyBotRHCall.cs create mode 100644 Services/ClassifyHandlers/ClassifyChat.cs create mode 100644 Services/ClassifyHandlers/ClassifyCompany.cs create mode 100644 Services/ClassifyHandlers/ClassifyEstimate.cs create mode 100644 Services/ClassifyHandlers/IHandlerClassify.cs create mode 100644 Services/ResponseService/IResponseService.cs create mode 100644 Services/ResponseService/ResponseBotRHService.cs create mode 100644 Services/ResponseService/ResponseChatService.cs create mode 100644 Services/ResponseService/ResponseCompanyService.cs create mode 100644 Services/ResponseService/ResponseFactory.cs create mode 100644 Services/TextClassifier.cs create mode 100644 Services/TextFilter.cs create mode 100644 SessionIdStore.cs create mode 100644 Settings/ChatRHSettings.cs create mode 100644 SharepointDomvsService.cs create mode 100644 TextoComEmbedding.cs create mode 100644 appsettings.Development.json create mode 100644 appsettings.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..98232e4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# SKEXP0070: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +dotnet_diagnostic.SKEXP0070.severity = silent diff --git a/AuthMiddleware.cs b/AuthMiddleware.cs new file mode 100644 index 0000000..350eb19 --- /dev/null +++ b/AuthMiddleware.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; + +namespace ChatApi +{ + public class AuthMiddleware + { + public void ConfigureServices(IServiceCollection services) + { + } + } +} diff --git a/ChatApi.csproj b/ChatApi.csproj new file mode 100644 index 0000000..2d9ea86 --- /dev/null +++ b/ChatApi.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + 10e5023f-8f45-46d6-8637-bc2127842068 + Linux + . + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChatApi.csproj.user b/ChatApi.csproj.user new file mode 100644 index 0000000..983ecfc --- /dev/null +++ b/ChatApi.csproj.user @@ -0,0 +1,9 @@ + + + + http + + + ProjectDebugger + + \ No newline at end of file diff --git a/ChatApi.http b/ChatApi.http new file mode 100644 index 0000000..b2d56ee --- /dev/null +++ b/ChatApi.http @@ -0,0 +1,6 @@ +@ChatApi_HostAddress = http://localhost:5020 + +GET {{ChatApi_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/ChatApi.sln b/ChatApi.sln new file mode 100644 index 0000000..7f588b2 --- /dev/null +++ b/ChatApi.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35122.118 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatApi", "ChatApi.csproj", "{B5287933-4BFA-4EC1-8522-393864C46B1F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B5287933-4BFA-4EC1-8522-393864C46B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5287933-4BFA-4EC1-8522-393864C46B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5287933-4BFA-4EC1-8522-393864C46B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5287933-4BFA-4EC1-8522-393864C46B1F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {332EC001-9CEA-4261-9DE0-110EEB502728} + EndGlobalSection +EndGlobal diff --git a/ChatHistoryService.cs b/ChatHistoryService.cs new file mode 100644 index 0000000..5eee0ea --- /dev/null +++ b/ChatHistoryService.cs @@ -0,0 +1,92 @@ +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using System.Text.Json; + +namespace ChatApi +{ + public class ChatHistoryService + { + private readonly Dictionary _keyValues = new Dictionary(); + public ChatHistoryService() + { + } + + public ChatHistory Get(string sessionId) + { + //var msg = new List(); + ////msg.Add(new ChatMessageContent(AuthorRole.System, "Your name is SuperChat. \nYou only generate answers using portuguese.\nYou are friendly and polite.\nYou speak only Brazilian Portuguese.\nYou never create a response in English. Always brazilian portuguese.")); + //msg.Add(new ChatMessageContent(AuthorRole.System, "Seu nome é SuperChat.")); + //msg.Add(new ChatMessageContent(AuthorRole.System, "Você só gera respostas usando português do Brasil.")); + //msg.Add(new ChatMessageContent(AuthorRole.System, "Você fala apenas português brasileiro.")); + //msg.Add(new ChatMessageContent(AuthorRole.System, "Você nunca cria respostas em inglês, mas sempre em português brasileiro.")); + //string json = JsonSerializer.Serialize(msg); + //var history = new ChatHistory(JsonSerializer.Deserialize>(json)); + + //return history; + + if (_keyValues.ContainsKey(sessionId)) + { + var history = _keyValues[sessionId]; + return history; + } + else + { + var msg = new List(); + TestePrompt(msg); + string json = JsonSerializer.Serialize(msg); + var history = new ChatHistory(JsonSerializer.Deserialize>(json)); + _keyValues[sessionId] = history; + return _keyValues[sessionId]; + } + } + + public ChatHistory GetSumarizer(string sessionId) + { + if (_keyValues.ContainsKey(sessionId)) + { + var history = _keyValues[sessionId]; + return history; + } + else + { + var msg = new List(); + PromptIan(msg); + string json = JsonSerializer.Serialize(msg); + var history = new ChatHistory(JsonSerializer.Deserialize>(json)); + _keyValues[sessionId] = history; + return _keyValues[sessionId]; + } + } + + public void UpdateHistory(string sessionId, ChatHistory history) + { + _keyValues[sessionId] = history; + } + + public void TestePromptBot(List msg) + { + msg.Add(new ChatMessageContent(AuthorRole.System, "Seu nome é Rosa, uma vendedora de bolos que está atendendo seus clientes. ")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Você tem apenas os seguintes sabores de bolo: chocolate, baunilha e morango. ")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Excepcionalmente hoje, o sabor morango está fora de estoque. Você não tem mais morangos. ")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Cada fatia de bolo custa 5 reais. \n")); + msg.Add(new ChatMessageContent(AuthorRole.User, "Responda sempre em portugues do Brasil as minhas perguntas.")); + } + + public void PromptIan(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, "Em breve, você será capaz de consultar o linkedin.")); + msg.Add(new ChatMessageContent(AuthorRole.User, "Use sempre portugues do Brasil.")); + } + + public void TestePrompt(List msg) + { + msg.Add(new ChatMessageContent(AuthorRole.System, "Seu nome é Ian.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Você só gera respostas usando português do Brasil.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Você fala apenas português brasileiro.")); + msg.Add(new ChatMessageContent(AuthorRole.System, "Você nunca cria respostas em inglês, mas sempre em português brasileiro.")); + msg.Add(new ChatMessageContent(AuthorRole.User, "Responda sempre em portugues do Brasil as minhas perguntas.")); + } + } +} diff --git a/ChatHistoryStore.cs b/ChatHistoryStore.cs new file mode 100644 index 0000000..16f992c --- /dev/null +++ b/ChatHistoryStore.cs @@ -0,0 +1,7 @@ +namespace ChatApi +{ + public class ChatHistoryStore + { + + } +} diff --git a/ChatRequest.cs b/ChatRequest.cs new file mode 100644 index 0000000..b6dd109 --- /dev/null +++ b/ChatRequest.cs @@ -0,0 +1,7 @@ +using System; + +public class ChatRequest +{ + public string SessionId { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; +} diff --git a/Controllers/ChatController.cs b/Controllers/ChatController.cs new file mode 100644 index 0000000..2a16dfc --- /dev/null +++ b/Controllers/ChatController.cs @@ -0,0 +1,95 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Embeddings; +using Microsoft.SemanticKernel.Memory; +using Microsoft.SemanticKernel.Connectors.InMemory; +using System.Globalization; +using System.Text; +using MongoDB.Driver; +using System.IO; +using System.Text.RegularExpressions; +using ChatApi.Services; +using ChatApi.Services.ResponseService; +using ChatApi.Services.Classifier; +using Microsoft.AspNetCore.Authorization; +using System.Security.Claims; +using ChatApi.Models; + +#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]")] + public class ChatController : ControllerBase + { + private readonly ILogger _logger; + private readonly TextFilter _textFilter; + private readonly ResponseFactory _responseFactory; + private readonly ClassifierPersistence _classifierPersistence; + + public ChatController( + ILogger logger, + TextFilter textFilter, + ResponseFactory responseFactory, + ClassifierPersistence classifierPersistence) + { + _logger = logger; + _textFilter = textFilter; + _responseFactory = responseFactory; + _classifierPersistence = classifierPersistence; + } + + [HttpGet(Name = "Response")] + public async Task Get([FromQuery] ChatRequest chatRequest) + { + var userData = UserData.Create(User); + var textClassifier = new TextClassifier(_textFilter, _classifierPersistence); + var textType = await textClassifier.ClassifyQuestion(chatRequest.SessionId, chatRequest.Message); + var responseText = _responseFactory.GetService(textType); + var response = await responseText.GetResponse(HttpContext, userData, chatRequest.SessionId, chatRequest.Message); + return response; + } + + [HttpPost(Name = "LoadDBData")] + public async Task SaveData([FromQuery] DBLoadRequest loadRequest) + { + //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); + } + } + } + } +} +#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/ChatController.cs~RF39ccb1.TMP b/Controllers/ChatController.cs~RF39ccb1.TMP new file mode 100644 index 0000000..f682375 --- /dev/null +++ b/Controllers/ChatController.cs~RF39ccb1.TMP @@ -0,0 +1,68 @@ +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/DBLoadRequest.cs b/DBLoadRequest.cs new file mode 100644 index 0000000..8be824e --- /dev/null +++ b/DBLoadRequest.cs @@ -0,0 +1,7 @@ +namespace ChatApi +{ + public class DBLoadRequest + { + public string Content { get; set; } = string.Empty; + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f4b0d66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["ChatApi.csproj", "."] +RUN dotnet restore "./ChatApi.csproj" +COPY . . +WORKDIR "/src/." +RUN dotnet build "./ChatApi.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./ChatApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "ChatApi.dll"] \ No newline at end of file diff --git a/DomvsDatabaseSettings.cs b/DomvsDatabaseSettings.cs new file mode 100644 index 0000000..eeb97bd --- /dev/null +++ b/DomvsDatabaseSettings.cs @@ -0,0 +1,15 @@ +namespace ChatApi +{ + public class DomvsDatabaseSettings + { + public string ConnectionString { get; set; } = null!; + + public string DatabaseName { get; set; } = null!; + + public string SharepointCollectionName { get; set; } = null!; + + public string ChatBotRHCollectionName { get; set; } = null!; + + public string ClassifierCollectionName { get; set; } = null!; + } +} diff --git a/Infra/Result.cs b/Infra/Result.cs new file mode 100644 index 0000000..0c9055a --- /dev/null +++ b/Infra/Result.cs @@ -0,0 +1,56 @@ +namespace ChatApi.Infra +{ + public class Result + { + protected Result(bool success, string error) + { + if (success && error != string.Empty) + throw new InvalidOperationException(); + if (!success && error == string.Empty) + throw new InvalidOperationException(); + Success = success; + Error = error; + } + + public bool Success { get; } + public string Error { get; } + public bool IsFailure => !Success; + + public static Result Fail(string message) + { + return new Result(false, message); + } + + public static Result Fail(string message) + { + return new Result(default, false, message); + } + + public static Result Ok() + { + return new Result(true, string.Empty); + } + + public static Result Ok(T value) + { + return new Result(value, true, string.Empty); + } + } + + public class Result : Result + { + protected internal Result(T value, bool success, string error) + : base(success, error) + { + Value = value; + } + + protected internal Result(T value) + : base(true, "") + { + Value = value; + } + + public T Value { get; set; } + } +} diff --git a/Models/UserData.cs b/Models/UserData.cs new file mode 100644 index 0000000..a1e9052 --- /dev/null +++ b/Models/UserData.cs @@ -0,0 +1,30 @@ +using System.Security.Claims; + +namespace ChatApi.Models +{ + public class UserData + { + private UserData() + { + } + + public string Email { get; set; } + public string Name { get; set; } + + public static UserData Create(ClaimsPrincipal user) + { + 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 + }; + } + } +} diff --git a/Models/UserData.cs~RF3dab6a.TMP b/Models/UserData.cs~RF3dab6a.TMP new file mode 100644 index 0000000..c6f962b --- /dev/null +++ b/Models/UserData.cs~RF3dab6a.TMP @@ -0,0 +1,30 @@ +using System.Security.Claims; + +namespace ChatApi.Models +{ + public class UserData + { + private UserData() + { + } + + public int Email { get; set; } + public int Name { get; set; } + + public static UserData Create(ClaimsPrincipal user) + { + 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 + }; + } + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..52e4030 --- /dev/null +++ b/Program.cs @@ -0,0 +1,157 @@ +using ChatApi; +using ChatApi.Services; +using ChatApi.Services.Bot; +using ChatApi.Services.Bot.Structs; +using ChatApi.Services.Classifier; +using ChatApi.Services.ResponseService; +using ChatApi.Settings; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using OllamaSharp; + +#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. + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.Configure( +builder.Configuration.GetSection("DomvsDatabase")); + +builder.Services.Configure( +builder.Configuration.GetSection("ChatRHSettings")); + +builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + + +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435")); +//var apiClient = new OllamaApiClient(new Uri("http://localhost:11435"), "tinydolphin"); +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinyllama", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("starling-lm", new Uri("http://localhost:11435")); + +//ServerSpace - GPT Service +builder.Services.AddOpenAIChatCompletion("openchat-3.5-0106", new Uri("https://gpt.serverspace.com.br/v1/chat/completions"), "tIAXVf3AkCkkpSX+PjFvktfEeSPyA1ZYam50UO3ye/qmxVZX6PIXstmJsLZXkQ39C33onFD/81mdxvhbGHm7tQ=="); + +builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +//builder.Services.AddOpenAIChatCompletion("gpt-4o-mini", "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"); +//builder.Services.AddGoogleAIGeminiChatCompletion("gemini-1.5-flash-latest", "AIzaSyDKBMX5yW77vxJFVJVE-5VLxlQRxCepck8"); + +//Anthropic / Claude +//builder.Services.AddAnthropicChatCompletion( +// modelId: "claude-3-5-sonnet-latest", // ou outro modelo Claude desejado +// apiKey: "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" +//); + +builder.Services.AddKernel(); +//builder.Services.AddKernel() +// .AddOllamaChatCompletion("phi3", new Uri("http://localhost:11435")) +// .AddOllamaTextEmbeddingGeneration() +// .Build(); + +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://192.168.0.150:11436")); + +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 + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = $"https://login.microsoftonline.com/{tenantId}/v2.0", + ValidAudience = "api://" + clientId + }; + }); + +builder.Services.AddControllers(); + +//builder.Services.AddAuthentication(options => +// { +// options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; +// options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +// }) +// .AddJwtBearer(options => +// { +// // Configurações anteriores... + +// // Eventos para log e tratamento de erros +// options.Events = new JwtBearerEvents +// { +// OnAuthenticationFailed = context => +// { +// // Log de erros de autenticação +// Console.WriteLine($"Erro de autenticação: {context.Exception.Message}"); +// return Task.CompletedTask; +// }, +// OnTokenValidated = context => +// { +// // Validações adicionais se necessário +// return Task.CompletedTask; +// } +// }; +// }); + + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Use(async (context, next) => +{ + var cookieOpt = new CookieOptions() + { + Path = "/", + Expires = DateTimeOffset.UtcNow.AddDays(1), + IsEssential = true, + HttpOnly = false, + Secure = false, + }; + + await next(); +}); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.Run(); + +#pragma warning restore SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/Program.cs~RF419917.TMP b/Program.cs~RF419917.TMP new file mode 100644 index 0000000..b9099d9 --- /dev/null +++ b/Program.cs~RF419917.TMP @@ -0,0 +1,61 @@ +using ChatApi; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using OllamaSharp; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddSingleton(); +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435")); +//var apiClient = new OllamaApiClient(new Uri("http://localhost:11435"), "tinydolphin"); +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinyllama", new Uri("http://localhost:11435")); +builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11435")); +builder.Services.AddKernel(); +//builder.Services.AddKernel() +// .AddOllamaChatCompletion("phi3", new Uri("http://localhost:11435")) +// .AddOllamaTextEmbeddingGeneration() +// .Build(); + +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://192.168.0.150:11436")); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Use(async (context, next) => +{ + var cookieOpt = new CookieOptions() + { + Path = "/", + Expires = DateTimeOffset.UtcNow.AddDays(1), + IsEssential = true, + HttpOnly = false, + Secure = false, + }; + + await next(); +}); + +app.Run(); diff --git a/Program.cs~RF68750a0.TMP b/Program.cs~RF68750a0.TMP new file mode 100644 index 0000000..e76aa24 --- /dev/null +++ b/Program.cs~RF68750a0.TMP @@ -0,0 +1,98 @@ +using ChatApi; +using ChatApi.Services; +using ChatApi.Services.Bot; +using ChatApi.Services.Bot.Structs; +using ChatApi.Services.Classifier; +using ChatApi.Services.ResponseService; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using OllamaSharp; + +#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. + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.Configure( +builder.Configuration.GetSection("DomvsDatabase")); + +builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + + +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435")); +//var apiClient = new OllamaApiClient(new Uri("http://localhost:11435"), "tinydolphin"); +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("tinyllama", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("starling-lm", new Uri("http://localhost:11435")); + +//ServerSpace - GPT Service +builder.Services.AddOpenAIChatCompletion("openchat-3.5-0106", new Uri("https://gpt.serverspace.com.br/v1/chat/completions"), "tIAXVf3AkCkkpSX+PjFvktfEeSPyA1ZYam50UO3ye/qmxVZX6PIXstmJsLZXkQ39C33onFD/81mdxvhbGHm7tQ=="); + +builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11435")); +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435")); +//builder.Services.AddOpenAIChatCompletion("gpt-4o-mini", "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"); +//builder.Services.AddGoogleAIGeminiChatCompletion("gemini-1.5-flash-latest", "AIzaSyDKBMX5yW77vxJFVJVE-5VLxlQRxCepck8"); + +//Anthropic / Claude +//builder.Services.AddAnthropicChatCompletion( +// modelId: "claude-3-5-sonnet-latest", // ou outro modelo Claude desejado +// apiKey: "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" +//); + +builder.Services.AddKernel(); +//builder.Services.AddKernel() +// .AddOllamaChatCompletion("phi3", new Uri("http://localhost:11435")) +// .AddOllamaTextEmbeddingGeneration() +// .Build(); + +//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://192.168.0.150:11436")); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Use(async (context, next) => +{ + var cookieOpt = new CookieOptions() + { + Path = "/", + Expires = DateTimeOffset.UtcNow.AddDays(1), + IsEssential = true, + HttpOnly = false, + Secure = false, + }; + + await next(); +}); + +app.Run(); + +#pragma warning restore SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..ed836dc --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,52 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5020" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7163;http://localhost:5020" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:61198", + "sslPort": 44305 + } + } +} \ No newline at end of file diff --git a/Services/Bot/5vb0vxog.ejb~ b/Services/Bot/5vb0vxog.ejb~ new file mode 100644 index 0000000..bcd8fd4 --- /dev/null +++ b/Services/Bot/5vb0vxog.ejb~ @@ -0,0 +1,64 @@ +using ChatApi.Services.Bot.Structs; + +namespace ChatApi.Services.Bot +{ + public class ChatBotRHCall + { + private readonly ChatBot _chatbot; + + Dictionary _opcoesSolicitação = + new Dictionary { + { "Inclusão/Exclusão Alura", "Inclusão/Exclusão Alura"}, + { "Inclusão/Exclusão Auxílio Idioma", "Inclusão/Exclusão Auxílio Idioma"}, + { "Inclusão/Exclusão Plano Odonto", "Inclusão/Exclusão Plano Odonto"}, + //{ "Envio de Atestado", "Envio de Atestado"}, + //{ "Envio de Reembolso Idioma", "Envio de Reembolso Idioma"}, + //{ "Afastamento e Licenças", "Afastamento e Licenças"}, + { "Dúvidas Plano de Saúde", "Dúvidas Plano de Saúde"}, + { "Dúvidas Folha de Pagamento", "Dúvidas Folha de Pagamento"}, + { "Ajuste Cargo/Salário", "Ajuste Cargo/Salário"}, + { "Feedbacks, reclamações", "1 - Feedbacks, reclamações"}, + { "Feedz - Dúvida 1:1", "Feedz - Dúvida 1:1"}, + { "Feedz - Dúvida Feedback", "Feedz - Dúvida Feedback"}, + { "Feedz - Cadastro de Objetivo", "Feedz - Cadastro de Objetivo"}, + { "Feedz - Erro Cadastro", "Feedz - Erro Cadastro"}, + { "Feedz - Outros", "Feedz - Outros"}, + { "Ciclo de Performance", "Ciclo de Performance"}, + { "Declaração Bolsa Faculdade", "Declaração Bolsa Faculdade"}, + { "Inclusão/Exclusão Programa de Especialização", "Inclusão/Exclusão Programa de Especialização"}, + //{ "Envio de Reembolso Especialização", "Envio de Reembolso Especialização"}, + { "DayOff", "DayOff"} + }; + + public ChatBotRHCall(ChatBot chatbot) + { + _chatbot = chatbot; + AddNumbersBeforShow(_opcoesSolicitação); + + _chatbot.AddQuestion(new Question( + "Qual seu número de celular (com WhatsApp)?", + resposta => !string.IsNullOrEmpty(resposta) && resposta.Length >= 10)); + + _chatbot.AddQuestion(new Question( + "Qual o tipo de solicitação (1 para Reembolso de Idioma, 2 para Reembolso Alura, 3 para Dúvidas)?", + resposta => resposta == "1" || resposta == "2" || resposta == "3")); + + _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))); + } + + private void AddNumbersBeforShow(Dictionary dict) + { + int optionNumber = 1; + foreach (var key in dict.Keys) + { + dict[key] = $"{optionNumber} - {dict[key]}"; + } + } + } +} diff --git a/Services/Bot/ActionCreateCall.cs b/Services/Bot/ActionCreateCall.cs new file mode 100644 index 0000000..001287f --- /dev/null +++ b/Services/Bot/ActionCreateCall.cs @@ -0,0 +1,63 @@ +using ChatApi.Infra; +using ChatApi.Services.Bot.Structs; +using ChatApi.Settings; +using System.Text; + +namespace ChatApi.Services.Bot +{ + public class ActionCreateCall : IActionCall + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ChatRHSettings _configuration; + private dynamic _callRH; + + public ActionCreateCall(IHttpClientFactory httpClientFactory, ChatRHSettings configuration) + { + _httpClientFactory = httpClientFactory; + _configuration = configuration; + } + + public Task> Call() + { + var httpClient =_httpClientFactory.CreateClient(); + var url = new Uri(new Uri(_configuration.Url), _configuration.Create); + var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Content = new StringContent(System.Text.Json.JsonSerializer.Serialize(_callRH), Encoding.UTF8, "application/json"); + var response = httpClient.SendAsync(request).Result; + + if (response.IsSuccessStatusCode) + { + var content = response.Content.ReadAsStringAsync().Result; + return Task.FromResult(Result.Ok(content)); + } + + var errorContent = ""; + try + { + errorContent = response.Content.ReadAsStringAsync().Result; + } + catch (Exception ex) + { + errorContent = "Náo foi possivel criar sua solicitação. Reinicie."; + } + + return Task.FromResult(Result.Fail(errorContent)); + } + + public Task Populate(Dictionary? knowParameters, Dictionary answers) + { + if (knowParameters == null) return Task.FromResult(Result.Fail("Nome e/ou email não foram informados.")); + + _callRH = new + { + Nome = knowParameters["Nome"], + Email = knowParameters["Email"], + WhatsApp = answers[0], + TipoSolicitacao = answers[1], + Descricao = answers[2], + }; + + return Task.FromResult(Result.Ok()); + } + } +} diff --git a/Services/Bot/ChatBotRHCall.cs b/Services/Bot/ChatBotRHCall.cs new file mode 100644 index 0000000..b4adb1a --- /dev/null +++ b/Services/Bot/ChatBotRHCall.cs @@ -0,0 +1,120 @@ +using ChatApi.Models; +using ChatApi.Services.Bot.Structs; +using ChatApi.Settings; +using Microsoft.Extensions.Options; +using Microsoft.SemanticKernel.ChatCompletion; +using System.Text; + +namespace ChatApi.Services.Bot +{ + public class ChatBotRHCall + { + private ChatBot _chatbot; + private readonly IChatCompletionService _chatCompletionService; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IOptions _chatRHApiConfig; + public UserData UserData { get; set; } + + Dictionary _opcoesSolicitação = + new Dictionary { + { "Inclusão/Exclusão Alura", "Inclusão/Exclusão Alura"}, + { "Inclusão/Exclusão Auxílio Idioma", "Inclusão/Exclusão Auxílio Idioma"}, + { "Inclusão/Exclusão Plano Odonto", "Inclusão/Exclusão Plano Odonto"}, + //{ "Envio de Atestado", "Envio de Atestado"}, + //{ "Envio de Reembolso Idioma", "Envio de Reembolso Idioma"}, + //{ "Afastamento e Licenças", "Afastamento e Licenças"}, + { "Dúvidas Plano de Saúde", "Dúvidas Plano de Saúde"}, + { "Dúvidas Folha de Pagamento", "Dúvidas Folha de Pagamento"}, + { "Ajuste Cargo/Salário", "Ajuste Cargo/Salário"}, + { "Feedbacks, reclamações", "Feedbacks, reclamações"}, + { "Feedz - Dúvida 1:1", "Feedz - Dúvida 1:1"}, + { "Feedz - Dúvida Feedback", "Feedz - Dúvida Feedback"}, + { "Feedz - Cadastro de Objetivo", "Feedz - Cadastro de Objetivo"}, + { "Feedz - Erro Cadastro", "Feedz - Erro Cadastro"}, + { "Feedz - Outros", "Feedz - Outros"}, + { "Ciclo de Performance", "Ciclo de Performance"}, + { "Declaração Bolsa Faculdade", "Declaração Bolsa Faculdade"}, + { "Inclusão/Exclusão Programa de Especialização", "Inclusão/Exclusão Programa de Especialização"}, + //{ "Envio de Reembolso Especialização", "Envio de Reembolso Especialização"}, + { "DayOff", "DayOff"} + }; + private readonly IOptions _config; + + public ChatBotRHCall( + IOptions config, + IChatCompletionService chatCompletionService, + IHttpClientFactory httpClientFactory, + IOptions chatRHApiConfig) + { + _chatCompletionService = chatCompletionService; + _httpClientFactory = httpClientFactory; + _chatRHApiConfig = chatRHApiConfig; + _config = config; + } + public string SetAnswer(string sessionId, string resposta) + { + if (_chatbot==null) + { + this.Init(sessionId); + } + + return _chatbot.SetAnswer(resposta); + } + + public async Task GetNextQuestion() + { + return await _chatbot.GetNextQuestion(); + } + + public string GetCurrentQuestion() + { + return _chatbot.GetCurrentQuestion(); + } + + public bool HasNextQuestion() + { + return _chatbot.HasNextQuestion(); + } + + public void Init(string sessionId) + { + var persist = ChatPersistence.Create(_config.Value).SetBotName("RHCall"); + var actionOnEnd = new ActionCreateCall(_httpClientFactory, _chatRHApiConfig.Value); + _chatbot = new ChatBot(persist, sessionId, this.UserData, _chatCompletionService, actionOnEnd); + + AddNumbersBeforShow(_opcoesSolicitação); + + _chatbot.AddQuestion(new Question( + "Você quer fazer uma solicitação ao RH, certo??", + resposta => !string.IsNullOrEmpty(resposta))); + + _chatbot.AddQuestion(new Question( + "Qual seu número de celular (com WhatsApp)?", + resposta => !string.IsNullOrEmpty(resposta) && resposta.Length >= 10)); + + var builder = new StringBuilder(); + _opcoesSolicitação.Select(s => s.Value).ToList().ForEach(s => builder.AppendLine(s)); + + _chatbot.AddQuestion(new Question( + $"Indique o tipo de solicitação: \n {builder}", + resposta => resposta == "1" || resposta == "2" || resposta == "3")); + + _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))); + } + private void AddNumbersBeforShow(Dictionary dict) + { + int optionNumber = 1; + foreach (var key in dict.Keys) + { + dict[key] = $"{optionNumber} - {dict[key]}"; + optionNumber++; + } + } + } +} diff --git a/Services/Bot/Structs/ChatBot.cs b/Services/Bot/Structs/ChatBot.cs new file mode 100644 index 0000000..7885d1f --- /dev/null +++ b/Services/Bot/Structs/ChatBot.cs @@ -0,0 +1,148 @@ +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 }, + { "Email", _userData.Email } + }; + } + + 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] = resposta; + _indiceAtual++; + _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++; + } + + 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~RFf86c93a.TMP b/Services/Bot/Structs/ChatBot.cs~RFf86c93a.TMP new file mode 100644 index 0000000..0957797 --- /dev/null +++ b/Services/Bot/Structs/ChatBot.cs~RFf86c93a.TMP @@ -0,0 +1,54 @@ +namespace ChatApi.Services.Bot.Structs +{ + public class ChatBot + { + private readonly List _Questions; + private readonly ChatPersistence _persistence; + private readonly Dictionary _answers; + private readonly Guid _usuarioId; + private int _indiceAtual; + + public ChatBot(ChatPersistence persistence, Guid usuarioId) + { + _Questions = new List(); + _answers = new Dictionary(); + _indiceAtual = 0; + _persistence = persistence; + _usuarioId = usuarioId; + + var estado = _persistence.GetState(_usuarioId); + if (estado != null) + { + _indiceAtual = estado.IndicePerguntaAtual; + _answers = estado.Respostas; + } + else + { + _indiceAtual = 0; + } + } + + public void AddQuestion(Question Question) + { + _Questions.Add(Question); + } + + public void Iniciar() + { + while (_indiceAtual < _Questions.Count) + { + var QuestionAtual = _Questions[_indiceAtual]; + if (QuestionAtual.TryToAnswer(out _)) + { + _indiceAtual++; + } + else + { + _indiceAtual = 0; // Reinicia o fluxo + } + } + + Console.WriteLine("Obrigado por responder a todas as perguntas!"); + } + } +} diff --git a/Services/Bot/Structs/ChatBot.cs~RFf89467e.TMP b/Services/Bot/Structs/ChatBot.cs~RFf89467e.TMP new file mode 100644 index 0000000..6c4eb52 --- /dev/null +++ b/Services/Bot/Structs/ChatBot.cs~RFf89467e.TMP @@ -0,0 +1,75 @@ +namespace ChatApi.Services.Bot.Structs +{ + public class ChatBot + { + private readonly List _questions; + private readonly ChatPersistence _persistence; + private readonly Dictionary _answers; + private readonly Guid _usuarioId; + private int _indiceAtual; + + public ChatBot(ChatPersistence persistence, Guid usuarioId) + { + _questions = new List(); + _answers = new Dictionary(); + _indiceAtual = 0; + _persistence = persistence; + _usuarioId = usuarioId; + + var estado = _persistence.GetState(_usuarioId); + if (estado != null) + { + _indiceAtual = estado.IndicePerguntaAtual; + _answers = estado.Respostas; + } + else + { + _indiceAtual = 0; + } + } + + public void AddQuestion(Question Question) + { + _questions.Add(Question); + } + + public bool InformarResposta(string resposta) + { + var perguntaAtual = _questions[_indiceAtual]; + if (perguntaAtual.Validator(resposta)) + { + _answers[_indiceAtual] = resposta; + _indiceAtual++; + + _persistence.SaveState(new ChatState + { + UsuarioId = _usuarioId, + IndicePerguntaAtual = _indiceAtual, + Respostas = new Dictionary(_answers) + }); + + return true; + } + + Console.WriteLine("Resposta inválida. Tente novamente."); + return false; + } + public void Iniciar() + { + while (_indiceAtual < _questions.Count) + { + var QuestionAtual = _questions[_indiceAtual]; + if (QuestionAtual.TryToAnswer(out _)) + { + _indiceAtual++; + } + else + { + _indiceAtual = 0; // Reinicia o fluxo + } + } + + Console.WriteLine("Obrigado por responder a todas as perguntas!"); + } + } +} diff --git a/Services/Bot/Structs/ChatPersistence.cs b/Services/Bot/Structs/ChatPersistence.cs new file mode 100644 index 0000000..ee7d554 --- /dev/null +++ b/Services/Bot/Structs/ChatPersistence.cs @@ -0,0 +1,45 @@ +using ChatApi.Services.Classifier; +using MongoDB.Driver; + +namespace ChatApi.Services.Bot.Structs +{ + public class ChatPersistence + { + private readonly IMongoCollection _collection; + private string _botName; + + private ChatPersistence(DomvsDatabaseSettings config) + { + var client = new MongoClient(config.ConnectionString); + var database = client.GetDatabase(config.DatabaseName); + _collection = database.GetCollection(config.ChatBotRHCollectionName); + } + + public static ChatPersistence Create(DomvsDatabaseSettings config) + { + return new ChatPersistence(config); + } + + public ChatPersistence SetBotName(string botName) + { + _botName = botName; + return this; + } + + public ChatState GetState(string usuarioId) + { + return _collection.Find(x => x.UsuarioId == usuarioId).FirstOrDefault(); + } + + public void DeleteState(string usuarioId) + { + _collection.DeleteOne(x => x.UsuarioId == usuarioId); + } + + public void SaveState(ChatState estado) + { + var filtro = Builders.Filter.Eq(x => x.UsuarioId, estado.UsuarioId); + _collection.ReplaceOne(filtro, estado, new ReplaceOptions { IsUpsert = true }); + } + } +} diff --git a/Services/Bot/Structs/ChatState.cs b/Services/Bot/Structs/ChatState.cs new file mode 100644 index 0000000..919b983 --- /dev/null +++ b/Services/Bot/Structs/ChatState.cs @@ -0,0 +1,20 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Options; + +namespace ChatApi.Services.Bot.Structs +{ + public class ChatState + { + [BsonId] + [BsonElement("_id")] + public string Id { get; set; } + public string UsuarioId { get; set; } + public int IndicePerguntaAtual { get; set; } + public int TentativasPerguntaAtual { get; set; } + + [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)] + public Dictionary Respostas { get; set; } + public Dictionary DadosConhecidos { get; set; } + } +} diff --git a/Services/Bot/Structs/IActionCall.cs b/Services/Bot/Structs/IActionCall.cs new file mode 100644 index 0000000..bef2fc3 --- /dev/null +++ b/Services/Bot/Structs/IActionCall.cs @@ -0,0 +1,10 @@ +using ChatApi.Infra; + +namespace ChatApi.Services.Bot.Structs +{ + public interface IActionCall + { + Task Populate(Dictionary? knowParameters, Dictionary answers); + Task> Call(); + } +} diff --git a/Services/Bot/Structs/Question.cs b/Services/Bot/Structs/Question.cs new file mode 100644 index 0000000..4156464 --- /dev/null +++ b/Services/Bot/Structs/Question.cs @@ -0,0 +1,42 @@ +namespace ChatApi.Services.Bot.Structs +{ + public class Question + { + public string Text { get; } + public Func Validator { get; } + public int CountTry { get; set; } + public Func, bool> Condition { get; } + + private const int MaxTentativas = 2; + + public Question(string texto, Func validador, Func, bool> condition=null) + { + Text = texto; + Validator = validador; + CountTry = 0; + Condition = condition; + } + + public bool MustShow(Dictionary respostasAnteriores) + { + return Condition == null || Condition(respostasAnteriores); + } + + public string TryToAnswer(string resposta) + { + if (Validator(resposta)) + { + return ""; + } + + CountTry++; + if (CountTry >= MaxTentativas) + { + CountTry = 0; + return "Não entendi sua resposta. Vamos reiniciar."; + } + + return "Resposta inválida. Tente novamente."; + } + } +} diff --git a/Services/Bot/Structs/e4zbjmgl.vwg~ b/Services/Bot/Structs/e4zbjmgl.vwg~ new file mode 100644 index 0000000..dd7b2a6 --- /dev/null +++ b/Services/Bot/Structs/e4zbjmgl.vwg~ @@ -0,0 +1,137 @@ +using Amazon.Auth.AccessControlPolicy; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace ChatApi.Services.Bot.Structs +{ + public class ChatBot + { + private readonly List _questions; + private readonly ChatPersistence _persistence; + private readonly Dictionary _knowParameters; + private readonly Dictionary _answers; + private readonly IChatCompletionService _chatCompletionService; + private readonly IActionCall _actionCall; + private readonly string _usuarioId; + private int _indiceAtual; + + public ChatBot(ChatPersistence persistence, string usuarioId, IChatCompletionService chatCompletionService, IActionCall actionCall) + { + _chatCompletionService = chatCompletionService; + _actionCall = actionCall; + _questions = new List(); + _answers = new Dictionary(); + _knowParameters = new Dictionary(); + _indiceAtual = 0; + _persistence = persistence; + _usuarioId = usuarioId; + + 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; + } + + 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] = resposta; + _indiceAtual++; + _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, + 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++; + } + + 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/v0pubwmg.zkp~ b/Services/Bot/Structs/v0pubwmg.zkp~ new file mode 100644 index 0000000..31225ba --- /dev/null +++ b/Services/Bot/Structs/v0pubwmg.zkp~ @@ -0,0 +1,148 @@ +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 }, + { "Email", _userData.Email } + }; + } + + 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] = resposta; + _indiceAtual++; + _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++; + } + + 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. : "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/tdpgs1ms.1ky~ b/Services/Bot/tdpgs1ms.1ky~ new file mode 100644 index 0000000..a4875ed --- /dev/null +++ b/Services/Bot/tdpgs1ms.1ky~ @@ -0,0 +1,64 @@ +using ChatApi.Services.Bot.Structs; + +namespace ChatApi.Services.Bot +{ + public class ChatBotRHCall + { + private readonly ChatBot _chatbot; + + Dictionary _opcoesSolicitação = + new Dictionary { + { "Inclusão/Exclusão Alura", "Inclusão/Exclusão Alura"}, + { "Inclusão/Exclusão Auxílio Idioma", "Inclusão/Exclusão Auxílio Idioma"}, + { "Inclusão/Exclusão Plano Odonto", "Inclusão/Exclusão Plano Odonto"}, + //{ "Envio de Atestado", "Envio de Atestado"}, + //{ "Envio de Reembolso Idioma", "Envio de Reembolso Idioma"}, + //{ "Afastamento e Licenças", "Afastamento e Licenças"}, + { "Dúvidas Plano de Saúde", "Dúvidas Plano de Saúde"}, + { "Dúvidas Folha de Pagamento", "Dúvidas Folha de Pagamento"}, + { "Ajuste Cargo/Salário", "Ajuste Cargo/Salário"}, + { "Feedbacks, reclamações", "1 - Feedbacks, reclamações"}, + { "Feedz - Dúvida 1:1", "Feedz - Dúvida 1:1"}, + { "Feedz - Dúvida Feedback", "Feedz - Dúvida Feedback"}, + { "Feedz - Cadastro de Objetivo", "Feedz - Cadastro de Objetivo"}, + { "Feedz - Erro Cadastro", "Feedz - Erro Cadastro"}, + { "Feedz - Outros", "Feedz - Outros"}, + { "Ciclo de Performance", "Ciclo de Performance"}, + { "Declaração Bolsa Faculdade", "Declaração Bolsa Faculdade"}, + { "Inclusão/Exclusão Programa de Especialização", "Inclusão/Exclusão Programa de Especialização"}, + //{ "Envio de Reembolso Especialização", "Envio de Reembolso Especialização"}, + { "DayOff", "DayOff"} + }; + + public ChatBotRHCall(ChatBot chatbot) + { + _chatbot = chatbot; + AddNumbersBeforShow(_opcoesSolicitação); + + _chatbot.AddQuestion(new Question( + "Qual seu número de celular (com WhatsApp)?", + resposta => !string.IsNullOrEmpty(resposta) && resposta.Length >= 10)); + + _chatbot.AddQuestion(new Question( + "Qual o tipo de solicitação (1 para Reembolso de Idioma, 2 para Reembolso Alura, 3 para Dúvidas)?", + resposta => resposta == "1" || resposta == "2" || resposta == "3")); + + _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?", + resposta => !string.IsNullOrEmpty(resposta))); + } + + private void AddNumbersBeforShow(Dictionary dict) + { + int optionNumber = 1; + foreach (var key in dict.Keys) + { + dict[key] = $"{optionNumber} - {dict[key]}"; + } + } + } +} diff --git a/Services/Classifier/ClassifierPersistence.cs b/Services/Classifier/ClassifierPersistence.cs new file mode 100644 index 0000000..631e0b3 --- /dev/null +++ b/Services/Classifier/ClassifierPersistence.cs @@ -0,0 +1,35 @@ +using ChatApi.Services.Bot.Structs; +using Microsoft.Extensions.Options; +using MongoDB.Driver; + +namespace ChatApi.Services.Classifier +{ + public class ClassifierPersistence + { + private readonly IMongoCollection _collection; + + public ClassifierPersistence(IOptions config) + { + var client = new MongoClient(config.Value.ConnectionString); + var database = client.GetDatabase(config.Value.DatabaseName); + _collection = database.GetCollection(config.Value.ClassifierCollectionName); + } + + public ClassifierSate GetState(string usuarioId) + { + return _collection.Find(x => x.Id == usuarioId).FirstOrDefault(); + } + + public void DeleteState(string usuarioId) + { + _collection.DeleteOne(x => x.UsuarioId == usuarioId); + } + + public void SaveState(ClassifierSate estado) + { + estado.Id = estado.UsuarioId; + var filtro = Builders.Filter.Eq(x => x.Id, estado.UsuarioId); + _collection.ReplaceOne(filtro, estado, new ReplaceOptions { IsUpsert = true }); + } + } +} diff --git a/Services/Classifier/ClassifierSate.cs b/Services/Classifier/ClassifierSate.cs new file mode 100644 index 0000000..1968dda --- /dev/null +++ b/Services/Classifier/ClassifierSate.cs @@ -0,0 +1,17 @@ +using ChatApi.Services.ClassifyHandlers; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace ChatApi.Services.Classifier +{ + public class ClassifierSate + { + [BsonId] + [BsonElement("_id")] + public string Id { get; set; } + public string UsuarioId { get; set; } + public EnumClassification Classification { get; set; } + public DateTime DhInicio { get; set; } + public EnumClassificationType ClassificationType { get; set; } + } +} diff --git a/Services/Classifier/ClassifierSate.cs~RF14991194.TMP b/Services/Classifier/ClassifierSate.cs~RF14991194.TMP new file mode 100644 index 0000000..cfdf7c0 --- /dev/null +++ b/Services/Classifier/ClassifierSate.cs~RF14991194.TMP @@ -0,0 +1,6 @@ +namespace ChatApi.Services.Classifier +{ + public class ClassifierSate + { + } +} diff --git a/Services/ClassifyHandlers/AClassifyHandler.cs b/Services/ClassifyHandlers/AClassifyHandler.cs new file mode 100644 index 0000000..1ea3d7a --- /dev/null +++ b/Services/ClassifyHandlers/AClassifyHandler.cs @@ -0,0 +1,50 @@ +namespace ChatApi.Services.ClassifyHandlers +{ + public enum EnumClassificationType + { + Stay, + Free + } + + public class AHandlerClassify : IHandlerClassify + { + private IHandlerClassify _nextHandlerClassify; + protected TextFilter _textFilter; + private EnumClassificationType _type = EnumClassificationType.Free; + + public EnumClassificationType Type + { + get + { + return _type; + } + protected set + { + _type = EnumClassificationType.Stay; + } + } + + public int StayInMinutes { get; set; } + public EnumClassification MyClassification { get; set; } + + public AHandlerClassify() + { + _textFilter = new TextFilter(); + } + + public IHandlerClassify SetNext(IHandlerClassify handler) + { + _nextHandlerClassify = handler; + return handler; + } + + public virtual EnumClassification Handle(string request) + { + if (_nextHandlerClassify != null) + { + return _nextHandlerClassify.Handle(request); + } + return EnumClassification.CantDetect; + } + } +} diff --git a/Services/ClassifyHandlers/ClassifyBotRHCall.cs b/Services/ClassifyHandlers/ClassifyBotRHCall.cs new file mode 100644 index 0000000..6111335 --- /dev/null +++ b/Services/ClassifyHandlers/ClassifyBotRHCall.cs @@ -0,0 +1,30 @@ +namespace ChatApi.Services.ClassifyHandlers +{ + public class ClassifyBotRHCall : AHandlerClassify + { + public ClassifyBotRHCall() + { + this.MyClassification = EnumClassification.BotRHCall; + this.Type = EnumClassificationType.Stay; + this.StayInMinutes = 240; + } + + public override EnumClassification Handle(string request) + { + var companyKeywords = new List { "solicitação", "solicitações", "pedido", "dúvida", "dúvidas", "alura", "auxilio idioma", "plano odonto", "envio atestado", "reembolso idioma", "afastamento", "Agility as a Service", "agilidade", + "plano de saúde", "folha de pagamento", "feedbacks", "reclamações", "1:1", "feedback", "Erro de cadsatro", "performance", + "bolsa faculdade", "especialização", "reembolso de especialização", "dayoff", "aws", "cadastro", "dúvidas" + }; + + var questionLower = base._textFilter.ToLowerAndWithoutAccents(request); + + if (questionLower.Contains(_textFilter.ToLowerAndWithoutAccents(" rh")) && + companyKeywords.Any(keyword => questionLower.Contains(_textFilter.ToLowerAndWithoutAccents(keyword)))) + { + return this.MyClassification; + } + + return base.Handle(request); + } + } +} diff --git a/Services/ClassifyHandlers/ClassifyChat.cs b/Services/ClassifyHandlers/ClassifyChat.cs new file mode 100644 index 0000000..1692a9d --- /dev/null +++ b/Services/ClassifyHandlers/ClassifyChat.cs @@ -0,0 +1,10 @@ +namespace ChatApi.Services.ClassifyHandlers +{ + public class ClassifyChat : AHandlerClassify + { + public override EnumClassification Handle(string request) + { + return EnumClassification.Chat; + } + } +} diff --git a/Services/ClassifyHandlers/ClassifyCompany.cs b/Services/ClassifyHandlers/ClassifyCompany.cs new file mode 100644 index 0000000..b6ad4a7 --- /dev/null +++ b/Services/ClassifyHandlers/ClassifyCompany.cs @@ -0,0 +1,25 @@ +namespace ChatApi.Services.ClassifyHandlers +{ + public class ClassifyCompany : AHandlerClassify + { + public ClassifyCompany() + { + this.MyClassification = EnumClassification.Company; + } + + public override EnumClassification Handle(string request) + { + var companyKeywords = new List { "Domvs", "DomvsiT", "squad gerenciada", "squad híbrida", "alocação", "Professional Service", "solução", "Soluções", "Agility as a Service", "agilidade", + "DDC", "Design Diamond Conception", "Bussiness Process Optmization", "Otimização de processos", "Soluções em Cloud", "Delphix", "UX", "Squad" }; + + var questionLower = base._textFilter.ToLowerAndWithoutAccents(request); + + if (companyKeywords.Any(keyword => questionLower.Contains(_textFilter.ToLowerAndWithoutAccents(keyword)))) + { + return this.MyClassification; + } + + return base.Handle(request); + } + } +} diff --git a/Services/ClassifyHandlers/ClassifyEstimate.cs b/Services/ClassifyHandlers/ClassifyEstimate.cs new file mode 100644 index 0000000..d5fc054 --- /dev/null +++ b/Services/ClassifyHandlers/ClassifyEstimate.cs @@ -0,0 +1,20 @@ +namespace ChatApi.Services.ClassifyHandlers +{ + public class ClassifyEstimate : AHandlerClassify + { + public override EnumClassification Handle(string request) + { + var companyKeywords = new List { "Domvs", "DomvsiT", "squad gerenciada", "squad híbrida", "alocação", "Professional Service", "solução", "Soluções", "Agility as a Service", "agilidade", + "DDC", "Design Diamond Conception", "Bussiness Process Optmization", "Otimização de processos", "Soluções em Cloud", "Delphix", "UX", "Squad" }; + + var questionLower = base._textFilter.ToLowerAndWithoutAccents(request); + + if (companyKeywords.Any(keyword => questionLower.Contains(_textFilter.ToLowerAndWithoutAccents(keyword)))) + { + return EnumClassification.Company; + } + + return base.Handle(request); + } + } +} diff --git a/Services/ClassifyHandlers/IHandlerClassify.cs b/Services/ClassifyHandlers/IHandlerClassify.cs new file mode 100644 index 0000000..ba2f104 --- /dev/null +++ b/Services/ClassifyHandlers/IHandlerClassify.cs @@ -0,0 +1,11 @@ +using System.Reflection.Metadata; + +namespace ChatApi.Services.ClassifyHandlers +{ + public interface IHandlerClassify + { + IHandlerClassify SetNext(IHandlerClassify handler); + + EnumClassification Handle(string request); + } +} diff --git a/Services/ResponseService/IResponseService.cs b/Services/ResponseService/IResponseService.cs new file mode 100644 index 0000000..bbc4fcd --- /dev/null +++ b/Services/ResponseService/IResponseService.cs @@ -0,0 +1,10 @@ +using ChatApi.Models; + +namespace ChatApi.Services.ResponseService +{ + public interface IResponseService + { + EnumClassification Classification { get; } + Task GetResponse(HttpContext context, UserData userData, string sessionId, string question); + } +} diff --git a/Services/ResponseService/ResponseBotRHService.cs b/Services/ResponseService/ResponseBotRHService.cs new file mode 100644 index 0000000..fbaeb24 --- /dev/null +++ b/Services/ResponseService/ResponseBotRHService.cs @@ -0,0 +1,76 @@ + +using ChatApi.Models; +using ChatApi.Services.Bot; +using ChatApi.Services.Bot.Structs; +using ChatApi.Services.Classifier; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Embeddings; +using OllamaSharp.Models.Chat; + +#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.Services.ResponseService +{ + public class ResponseBotRHService : IResponseService + { + private readonly ChatHistoryService _chatHistoryService; + private readonly TextFilter _textFilter; + private readonly ChatBotRHCall _chatBotCall; + private readonly ClassifierPersistence _classifierPersistence; + + public ResponseBotRHService( + ChatHistoryService chatHistoryService, + TextFilter textFilter, + ChatBotRHCall chatBotCall, + ClassifierPersistence classifierPersistence) + { + _chatHistoryService = chatHistoryService; + _textFilter = textFilter; + _chatBotCall = chatBotCall; + _classifierPersistence = classifierPersistence; + } + public EnumClassification Classification => EnumClassification.BotRHCall; + + public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question) + { + 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 resposta = ""; + if (string.IsNullOrEmpty(resp) && resp!="REINICIAR") + { + resposta = await _chatBotCall.GetNextQuestion(); + history.AddUserMessage(question); + + history.AddMessage(AuthorRole.Assistant, resposta ?? ""); + + _chatHistoryService.UpdateHistory(sessionId, history); + + if (!_chatBotCall.HasNextQuestion()) + { + _classifierPersistence.DeleteState(sessionId); + } + } + else + { + 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; + } + + stopWatch.Stop(); + return $"{resposta ?? ""}\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/Services/ResponseService/ResponseChatService.cs b/Services/ResponseService/ResponseChatService.cs new file mode 100644 index 0000000..59f9b27 --- /dev/null +++ b/Services/ResponseService/ResponseChatService.cs @@ -0,0 +1,42 @@ + +using ChatApi.Models; +using Microsoft.SemanticKernel.ChatCompletion; +using OllamaSharp.Models.Chat; +using System.Diagnostics; + +namespace ChatApi.Services.ResponseService +{ + public class ResponseChatService : IResponseService + { + private readonly ChatHistoryService _chatHistoryService; + private readonly IChatCompletionService _chatCompletionService; + + public ResponseChatService( + ChatHistoryService chatHistoryService, + IChatCompletionService chatCompletionService) + { + this._chatHistoryService = chatHistoryService; + this._chatCompletionService = chatCompletionService; + } + public EnumClassification Classification => EnumClassification.Chat; + + public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question) + { + var stopWatch = new System.Diagnostics.Stopwatch(); + + stopWatch.Start(); + + SessionIdStore sessionIdStore = new SessionIdStore(context); + ChatHistory history = _chatHistoryService.Get(sessionId); + + history.AddUserMessage(question); + 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"; + } + } +} diff --git a/Services/ResponseService/ResponseCompanyService.cs b/Services/ResponseService/ResponseCompanyService.cs new file mode 100644 index 0000000..db166ab --- /dev/null +++ b/Services/ResponseService/ResponseCompanyService.cs @@ -0,0 +1,99 @@ + +using ChatApi.Models; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Embeddings; +using OllamaSharp.Models.Chat; + +#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.Services.ResponseService +{ + public class ResponseCompanyService : IResponseService + { + private readonly ChatHistoryService _chatHistoryService; + private readonly Kernel _kernel; + private readonly TextFilter _textFilter; + private readonly SharepointDomvsService _sharepointDomvsService; + private readonly IChatCompletionService _chatCompletionService; + + public ResponseCompanyService( + ChatHistoryService chatHistoryService, + Kernel kernel, + TextFilter textFilter, + SharepointDomvsService sharepointDomvsService, + IChatCompletionService chatCompletionService) + { + this._chatHistoryService = chatHistoryService; + this._kernel = kernel; + this._textFilter = textFilter; + this._sharepointDomvsService = sharepointDomvsService; + this._chatCompletionService = chatCompletionService; + } + public EnumClassification Classification => EnumClassification.Company; + + public async Task GetResponse(HttpContext context, UserData userData, string sessionId, string question) + { + var stopWatch = new System.Diagnostics.Stopwatch(); + stopWatch.Start(); + + SessionIdStore sessionIdStore = new SessionIdStore(context); + + var resposta = await BuscarTextoRelacionado(question); + question = "Para responder à pergunta: \"" + question + "\" por favor, gere um resumo com 3 linhas baseado exclusivamente no texto: \"" + resposta + "\""; + ChatHistory history = _chatHistoryService.GetSumarizer(sessionId); + + history.AddUserMessage(question); + + 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"; + + } + + async Task BuscarTextoRelacionado(string pergunta) + { + var embeddingService = _kernel.GetRequiredService(); + var embeddingPergunta = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(pergunta)); + var embeddingArrayPergunta = embeddingPergunta.ToArray().Select(e => (double)e).ToArray(); + + var textos = await _sharepointDomvsService.GetAsync(); + + TextoComEmbedding melhorTexto = null; + double melhorSimilaridade = -1.0; + + foreach (var texto in textos) + { + double similaridade = CalcularSimilaridadeCoseno(embeddingArrayPergunta, texto.Embedding); + if (similaridade > melhorSimilaridade) + { + melhorSimilaridade = similaridade; + melhorTexto = texto; + } + } + + // 4. Retornar o conteúdo mais similar, ou uma mensagem padrão + return melhorTexto != null ? melhorTexto.Conteudo : "Não encontrei uma resposta adequada."; + } + + double CalcularSimilaridadeCoseno(double[] embedding1, double[] embedding2) + { + double dotProduct = 0.0; + double normA = 0.0; + double normB = 0.0; + for (int i = 0; i < embedding1.Length; i++) + { + dotProduct += embedding1[i] * embedding2[i]; + normA += Math.Pow(embedding1[i], 2); + normB += Math.Pow(embedding2[i], 2); + } + return dotProduct / (Math.Sqrt(normA) * Math.Sqrt(normB)); + } + } +} + +#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/Services/ResponseService/ResponseFactory.cs b/Services/ResponseService/ResponseFactory.cs new file mode 100644 index 0000000..f1fd60a --- /dev/null +++ b/Services/ResponseService/ResponseFactory.cs @@ -0,0 +1,18 @@ +using System.Net.NetworkInformation; + +namespace ChatApi.Services.ResponseService +{ + public class ResponseFactory + { + private readonly IEnumerable _responseServices; + public ResponseFactory(IEnumerable responseService) + { + _responseServices = responseService; + } + + public IResponseService? GetService(EnumClassification classification) + { + return _responseServices.FirstOrDefault(w => w.Classification == classification); + } + } +} diff --git a/Services/TextClassifier.cs b/Services/TextClassifier.cs new file mode 100644 index 0000000..0f7a925 --- /dev/null +++ b/Services/TextClassifier.cs @@ -0,0 +1,60 @@ +using ChatApi.Services.Classifier; +using ChatApi.Services.ClassifyHandlers; + +namespace ChatApi.Services +{ + public enum EnumClassification + { + Company=0, + Estimate=1, + BotRHCall = 2, + Chat = 3, + CantDetect=4 + } + + /// + /// enum + /// + public class TextClassifier + { + private readonly TextFilter _textFilter; + private readonly ClassifierPersistence _classifierPersistence; + + public TextClassifier(TextFilter textFilter, ClassifierPersistence classifierPersistence) + { + _textFilter = textFilter; + _classifierPersistence = classifierPersistence; + } + public async Task ClassifyQuestion(string sessionId, string question) + { + var state = _classifierPersistence.GetState(sessionId); + + if (state!=null && (state.ClassificationType == EnumClassificationType.Stay)) + { + return state.Classification; + } + else + { + var classifyCompany = new ClassifyCompany(); + var classifyChat = new ClassifyChat(); + var botChat = new ClassifyBotRHCall(); + + classifyCompany + .SetNext(botChat) + .SetNext(classifyChat); + + var classify = classifyCompany.Handle(question); + + _classifierPersistence.SaveState(new ClassifierSate + { + Id = sessionId, + UsuarioId = sessionId, + Classification = classify, + DhInicio = DateTime.Now, + ClassificationType = botChat.MyClassification == classify ? EnumClassificationType.Stay : EnumClassificationType.Free + }); + return classify; + } + } + } +} diff --git a/Services/TextFilter.cs b/Services/TextFilter.cs new file mode 100644 index 0000000..15a0594 --- /dev/null +++ b/Services/TextFilter.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using System.Text; + +namespace ChatApi.Services +{ + public class TextFilter + { + public string ToLowerAndWithoutAccents(string text) + { + return RemoveDiacritics(text.ToLower()); + } + + public string RemoveDiacritics(string text) + { + var normalizedString = text.Normalize(NormalizationForm.FormD); + var stringBuilder = new StringBuilder(capacity: normalizedString.Length); + + for (int i = 0; i < normalizedString.Length; i++) + { + char c = normalizedString[i]; + var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); + if (unicodeCategory != UnicodeCategory.NonSpacingMark) + { + stringBuilder.Append(c); + } + } + + return stringBuilder + .ToString() + .Normalize(NormalizationForm.FormC); + } + } +} diff --git a/SessionIdStore.cs b/SessionIdStore.cs new file mode 100644 index 0000000..fbd1b3a --- /dev/null +++ b/SessionIdStore.cs @@ -0,0 +1,32 @@ +namespace ChatApi +{ + public class SessionIdStore + { + HttpContext _context; + + public SessionIdStore(HttpContext httpContext) + { + _context = httpContext; + } + + public string GetSessionId() + { + var sessionId = _context.Request.Cookies["ChatSessionId"]; + + if (sessionId == null) + { + sessionId = Guid.NewGuid().ToString("N"); + _context.Response.Cookies.Append("ChatSessionId", sessionId, new CookieOptions + { + Expires = DateTimeOffset.Now.AddMonths(12), + HttpOnly = true, + SameSite = SameSiteMode.None, + Secure = true + }); + } + + + return sessionId; + } + } +} diff --git a/Settings/ChatRHSettings.cs b/Settings/ChatRHSettings.cs new file mode 100644 index 0000000..ca37ab1 --- /dev/null +++ b/Settings/ChatRHSettings.cs @@ -0,0 +1,8 @@ +namespace ChatApi.Settings +{ + public class ChatRHSettings + { + public string Url { get; set; } + public string Create { get; set; } + } +} diff --git a/SharepointDomvsService.cs b/SharepointDomvsService.cs new file mode 100644 index 0000000..e4956ca --- /dev/null +++ b/SharepointDomvsService.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Options; +using MongoDB.Driver; + +namespace ChatApi +{ + public class SharepointDomvsService + { + private readonly IMongoCollection _booksCollection; + + public SharepointDomvsService( + IOptions bookStoreDatabaseSettings) + { + var mongoClient = new MongoClient( + bookStoreDatabaseSettings.Value.ConnectionString); + + var mongoDatabase = mongoClient.GetDatabase( + bookStoreDatabaseSettings.Value.DatabaseName); + + _booksCollection = mongoDatabase.GetCollection( + bookStoreDatabaseSettings.Value.SharepointCollectionName); + } + + public async Task> GetAsync() => + await _booksCollection.Find(_ => true).ToListAsync(); + + public async Task GetAsync(string id) => + await _booksCollection.Find(x => x.Id == id).FirstOrDefaultAsync(); + + public async Task CreateAsync(TextoComEmbedding newBook) => + await _booksCollection.InsertOneAsync(newBook); + + public async Task UpdateAsync(string id, TextoComEmbedding updatedBook) => + await _booksCollection.ReplaceOneAsync(x => x.Id == id, updatedBook); + + public async Task RemoveAsync(string id) => + await _booksCollection.DeleteOneAsync(x => x.Id == id); + } +} diff --git a/TextoComEmbedding.cs b/TextoComEmbedding.cs new file mode 100644 index 0000000..62c5091 --- /dev/null +++ b/TextoComEmbedding.cs @@ -0,0 +1,16 @@ +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson; + +namespace ChatApi +{ + public class TextoComEmbedding + { + [BsonId] + [BsonElement("_id")] + [BsonRepresentation(BsonType.ObjectId)] + public string Id { get; set; } + + public string Conteudo { get; set; } + public double[] Embedding { get; set; } + } +} \ No newline at end of file diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..0f2755a --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,19 @@ +{ + "DomvsDatabase": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "DomvsSites", + "SharepointCollectionName": "SharepointSite", + "ChatBotRHCollectionName": "ChatBotRHData", + "ClassifierCollectionName": "ClassifierData" + }, + "ChatRHSettings": { + "Url": "https://localhost:7070/", + "Create": "/CallRH" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..4c39c1b --- /dev/null +++ b/appsettings.json @@ -0,0 +1,22 @@ +{ + "DomvsDatabase": { + "ConnectionString": "mongodb://localhost:27017", + "DatabaseName": "DomvsSites", + "SharepointCollectionName": "SharepointSite", + "ChatBotRHCollectionName": "ChatBotRHData", + "ClassifierCollectionName": "ClassifierData" + }, + "ChatRHSettings": { + "Url": "mongodb://localhost:27017", + "Create": "DomvsSites" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "AppTenantId": "20190830-5fd4-4a72-b8fd-1c1cb35b25bc", + "AppClientID": "8f4248fc-ee30-4f54-8793-66edcca3fd20" +}