diff --git a/Chat.Domain/Tools/Base64ToFormFile.cs b/Chat.Domain/Tools/Base64ToFormFile.cs new file mode 100644 index 0000000..6f4f566 --- /dev/null +++ b/Chat.Domain/Tools/Base64ToFormFile.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Chat.Domain.Tools +{ + public class Base64ToFormFile + { + public static IFormFile ConvertBase64ToFormFile(string base64String, string fileName) + { + // Remove o prefixo "data:image/png;base64," se existir + if (base64String.Contains(",")) + { + base64String = base64String.Split(',')[1]; + } + + // Converte base64 para bytes + byte[] bytes = Convert.FromBase64String(base64String); + + // Cria um stream de memória com os bytes + var stream = new MemoryStream(bytes); + + // Cria um IFormFile usando o stream + var formFile = new FormFile( + baseStream: stream, + baseStreamOffset: 0, + length: bytes.Length, + name: "file", + fileName: fileName + ) + { + Headers = new HeaderDictionary(), + ContentType = "image/png" + }; + + return formFile; + } + } +} diff --git a/Chat/ChatMvc.csproj b/Chat/ChatMvc.csproj index 6dc6eff..9d31b55 100644 --- a/Chat/ChatMvc.csproj +++ b/Chat/ChatMvc.csproj @@ -26,6 +26,7 @@ + @@ -36,6 +37,10 @@ + + + + True diff --git a/Chat/Controllers/ChatController.cs b/Chat/Controllers/ChatController.cs index 148a52d..d7f11bb 100644 --- a/Chat/Controllers/ChatController.cs +++ b/Chat/Controllers/ChatController.cs @@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Authorization; using System.Linq; using Microsoft.AspNetCore.Authentication; +using Chat.Domain.Tools; +using Newtonsoft.Json.Linq; namespace ChatMvc.Controllers { @@ -34,9 +36,9 @@ namespace ChatMvc.Controllers return View(); } - [HttpGet("chat/proxy-response")] + [HttpPost("chat/proxy-response")] [ValidateAntiForgeryToken] - public async Task ProxyResponse(string sessionId, string message) + public async Task ProxyResponse([FromForm] string sessionId, [FromForm] string message, [FromForm] IFormFile file1 = null, [FromForm] IFormFile file2 = null) { try { @@ -45,24 +47,60 @@ namespace ChatMvc.Controllers return BadRequest("Requisição inválida"); } - var userId = User.Claims.FirstOrDefault(f => f.Type == "UserId").Value; - var name = User.Claims.FirstOrDefault(f => f.Type == "FirstName").Value; - var company = User.Claims.FirstOrDefault(f => f.Type == "CompanyName").Value; - var token = User.Claims.FirstOrDefault(f => f.Type == "TokenExternal").Value; + var userId = User.Claims.FirstOrDefault(f => f.Type == "UserId")?.Value; + var name = User.Claims.FirstOrDefault(f => f.Type == "FirstName")?.Value; + var company = User.Claims.FirstOrDefault(f => f.Type == "CompanyName")?.Value; + var token = User.Claims.FirstOrDefault(f => f.Type == "TokenExternal")?.Value; + + if (string.IsNullOrEmpty(token)) + { + return BadRequest("Token não encontrado"); + } + + // Verificar se há arquivos e palavras-chave + var keyWords = new[] { "diferença", "diferenças", "comparação", "comparar", "compare" }; + bool hasKeyword = keyWords.Any(k => message.ToLower().Contains(k.ToLower())); + bool hasBothFiles = file1 != null && file2 != null; + bool hasOneFile = file1 != null || file2 != null; + bool isPDF = file1 != null && file1.ContentType == "application/pdf" && file2 != null && file2.ContentType == "application/pdf"; + bool isImage = file1 != null && file1.ContentType.Contains("image") && file2 != null && file2.ContentType.Contains("image"); + + // Se tem apenas um arquivo ou se tem arquivos mas não tem palavra-chave + if ((hasOneFile && !hasBothFiles) || (hasBothFiles && !hasKeyword)) + { + return Ok("Por favor, especifique o que deseja fazer com os arquivos. " + + "Para comparar dois PDFs, inclua palavras como 'diferença' ou 'comparação' em sua mensagem."); + } var client = _httpClientFactory.CreateClient(); - var baseUrl = _configuration["ExternalApiBaseUrl"]; - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); - var response = await client.GetAsync( - $"{baseUrl}/chat/response?sessionId={sessionId}&message={message}"); + var baseUrl = _configuration["ExternalApiBaseUrl"]; + + if (hasBothFiles && hasKeyword && isPDF) + { + var content = await CompareFiles(baseUrl, sessionId, client, file1, file2); + return Ok(content); + } + else if (hasBothFiles && hasKeyword && isImage) + { + var content = await CompareFilesImage(baseUrl, sessionId, client, file1, file2); + return Ok(content); + } + + var chatContent = new FormUrlEncodedContent(new[] + { + new KeyValuePair("sessionId", sessionId), + new KeyValuePair("message", message) + }); + + var response = await client.PostAsync($"{baseUrl}/chat/response", chatContent); response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(); + var responseContent = await response.Content.ReadAsStringAsync(); - return Ok(content); + return Ok(responseContent); } catch (Exception ex) { @@ -70,6 +108,112 @@ namespace ChatMvc.Controllers } } + private async Task CompareFiles(string? baseUrl, string sessionId, HttpClient? client, [FromForm] IFormFile? file1 = null, [FromForm] IFormFile? file2 = null) + { + // Primeiro, comparar os PDFs + var formData = new MultipartFormDataContent(); + var content1 = new StreamContent(file1.OpenReadStream()); + var content2 = new StreamContent(file2.OpenReadStream()); + content1.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); + content2.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); + formData.Add(content1, "File1", file1.FileName); + formData.Add(content2, "File2", file2.FileName); + + var comparisonResponse = await client.PostAsync( + "https://localhost:7200/api/PdfComparison/compare", + formData + ); + + if (!comparisonResponse.IsSuccessStatusCode) + { + throw new Exception("Erro ao comparar os PDFs"); + } + + var differences = await comparisonResponse.Content.ReadAsStringAsync(); + var dynamicContent = JsonConvert.DeserializeObject(differences); + var formattedDifferences = FormatDifferencesForChat(dynamicContent); + + var promptMessage = $"Ao comparar dois textos obtive as diferenças do json. Você poderia explicar e resumir: {formattedDifferences}"; + + var chatContentSend = new FormUrlEncodedContent(new[] + { + new KeyValuePair("sessionId", sessionId), + new KeyValuePair("message", promptMessage) + }); + + var chatResponse = await client.PostAsync($"{baseUrl}/chat/response-fromia", chatContentSend); + + chatResponse.EnsureSuccessStatusCode(); + var content = await chatResponse.Content.ReadAsStringAsync(); + + return content; + } + + private async Task CompareFilesImage(string? baseUrl, string sessionId, HttpClient? client, [FromForm] IFormFile? file1 = null, [FromForm] IFormFile? file2 = null) + { + var formData = new MultipartFormDataContent(); + var content1 = new StreamContent(file1.OpenReadStream()); + var content2 = new StreamContent(file2.OpenReadStream()); + content1.Headers.ContentType = new MediaTypeHeaderValue(file1.ContentType); + content2.Headers.ContentType = new MediaTypeHeaderValue(file2.ContentType); + formData.Add(content1, "Drawing1", file1.FileName); + formData.Add(content2, "Drawing2", file2.FileName); + + var comparisonResponse = await client.PostAsync( + "https://localhost:7200/api/TechnicalDrawing/compare-images", + formData + ); + + if (!comparisonResponse.IsSuccessStatusCode) + { + throw new Exception("Erro ao comparar os PDFs"); + } + + var differences = await comparisonResponse.Content.ReadAsStringAsync(); + JObject obj = JObject.Parse(differences); + string image = obj["image"].ToString(); + + var file3 = Base64ToFormFile.ConvertBase64ToFormFile(image, "compared.png"); + + var promptMessage = $"Ao comparar dois desenhos técnicos, obtive as diferenças entre eles. Você precisa explicar e resumir estas diferenças ressaltando mudanças em medidas da peça. O arquivo "; + + var response = ""; + using (formData = AddFormDataToResponse(sessionId, promptMessage, file1, file2, file3)) + { + var chatResponse = await client.PostAsync($"{baseUrl}/chat/response-fromia", formData); + chatResponse.EnsureSuccessStatusCode(); + response = await chatResponse.Content.ReadAsStringAsync(); + } + return response; + } + + private MultipartFormDataContent AddFormDataToResponse(string sessionId, string message, IFormFile? file1 = null, IFormFile? file2 = null, IFormFile? file3 = null) + { + var formData = new MultipartFormDataContent(); + formData.Add(new StringContent(sessionId), "SessionId"); + formData.Add(new StringContent(message), "Message"); + + if (file1 != null) + { + var streamContent1 = new StreamContent(file1.OpenReadStream()); + formData.Add(streamContent1, "File1", file1.FileName); + } + + if (file2 != null) + { + var streamContent2 = new StreamContent(file2.OpenReadStream()); + formData.Add(streamContent2, "File2", file2.FileName); + } + + if (file3 != null) + { + var streamContent3 = new StreamContent(file3.OpenReadStream()); + formData.Add(streamContent3, "File3", file3.FileName); + } + + return formData; + } + [HttpPost("chat/authenticate")] [ValidateAntiForgeryToken] public async Task Authenticate([FromBody] AuthenticateRequest request) @@ -125,6 +269,53 @@ namespace ChatMvc.Controllers return BadRequest($"Erro na autenticação: {ex.Message}"); } } + + private string FormatDifferencesForChat(dynamic jsonDifferences) + { + var sb = new StringBuilder(); + sb.AppendLine("Análise das diferenças entre os documentos:"); + sb.AppendLine(); + + try + { + // Focar apenas nas diferenças reais (onlyDifferences) + var onlyDiffs = jsonDifferences.onlyDifferences; + + if (onlyDiffs != null && onlyDiffs.Count > 0) + { + foreach (var diff in onlyDiffs) + { + string content = diff.content?.ToString() ?? ""; + string type = diff.typeName?.ToString() ?? ""; + int lineNumber = diff.lineNumber; + + switch (type) + { + case "Deleted": + sb.AppendLine($"Removido (linha {lineNumber}):"); + sb.AppendLine($"- {content}"); + break; + case "Inserted": + sb.AppendLine($"Adicionado (linha {lineNumber}):"); + sb.AppendLine($"+ {content}"); + break; + } + sb.AppendLine(); + } + } + else + { + sb.AppendLine("Nenhuma diferença significativa encontrada entre os documentos."); + } + } + catch (Exception ex) + { + sb.AppendLine("Erro ao processar diferenças. Resumo simplificado:"); + sb.AppendLine("As diferenças entre os documentos não puderam ser formatadas adequadamente."); + } + + return sb.ToString(); + } } public class AuthenticateRequest diff --git a/Chat/Controllers/DocumentsController.cs b/Chat/Controllers/DocumentsController.cs index 8291fa7..93cbc72 100644 --- a/Chat/Controllers/DocumentsController.cs +++ b/Chat/Controllers/DocumentsController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using ChatMvc.Managers; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Net.Http.Headers; using static System.Net.Mime.MediaTypeNames; @@ -9,17 +10,24 @@ namespace ChatMvc.Controllers public class DocumentsController : Controller { private readonly HttpClient _httpClient; + private readonly TokenManager _tokenManager; - public DocumentsController() + public DocumentsController(TokenManager tokenManager) { _httpClient = new HttpClient(); _httpClient.BaseAddress = new Uri("http://localhost:5020/"); + this._tokenManager = tokenManager; } public async Task Index() { var token = User.Claims.FirstOrDefault(f => f.Type == "TokenExternal").Value; + if (_tokenManager.IsTokenExpired(token)) { + var userId = User.Claims.FirstOrDefault(f => f.Type == "TokenExternal").Value; + token = await _tokenManager.GetToken(User); + } + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); diff --git a/Chat/Managers/TokenManager.cs b/Chat/Managers/TokenManager.cs index bec4634..11dc15b 100644 --- a/Chat/Managers/TokenManager.cs +++ b/Chat/Managers/TokenManager.cs @@ -1,7 +1,9 @@ using ChatMvc.Controllers; using Newtonsoft.Json; using Stripe.Forwarding; +using System.IdentityModel.Tokens.Jwt; using System.Net.Http; +using System.Security.Claims; using System.Text; namespace ChatMvc.Managers @@ -59,5 +61,61 @@ namespace ChatMvc.Managers return tokenResult.Token; } + + public async Task GetToken(ClaimsPrincipal user) + { + var userId = user.Claims.FirstOrDefault(f => f.Type == "UserId")?.Value; + var name = user.Claims.FirstOrDefault(f => f.Type == "FirstName")?.Value; + var company = user.Claims.FirstOrDefault(f => f.Type == "CompanyName")?.Value; + + var client = _httpClientFactory.CreateClient(); + var baseUrl = _configuration["ExternalApiBaseUrl"]; + + // Primeira requisição - newclient + var newClientRequest = new + { + localId = userId, + companyTenant = company, + name = name + }; + + var newClientResponse = await client.PostAsync( + $"{baseUrl}/login/newclient", + new StringContent(JsonConvert.SerializeObject(newClientRequest), + Encoding.UTF8, "application/json")); + + newClientResponse.EnsureSuccessStatusCode(); + var clientContent = await newClientResponse.Content.ReadAsStringAsync(); + var clientResult = JsonConvert.DeserializeObject(clientContent); + + // Segunda requisição - token + var tokenRequest = new + { + clientId = userId, + clientName = name, + clientSecret = clientResult.Secret + }; + + var tokenResponse = await client.PostAsync( + $"{baseUrl}/login/token", + new StringContent(JsonConvert.SerializeObject(tokenRequest), + Encoding.UTF8, "application/json")); + + tokenResponse.EnsureSuccessStatusCode(); + var tokenContent = await tokenResponse.Content.ReadAsStringAsync(); + var tokenResult = JsonConvert.DeserializeObject(tokenContent); + + return tokenResult.Token; + + } + + public bool IsTokenExpired(string token) + { + var handler = new JwtSecurityTokenHandler(); + var jwtToken = handler.ReadJwtToken(token); + + var expiracao = jwtToken.ValidTo; + return expiracao < DateTime.UtcNow; + } } } diff --git a/Chat/Program.cs b/Chat/Program.cs index c28f7c3..3afb9ac 100644 --- a/Chat/Program.cs +++ b/Chat/Program.cs @@ -19,7 +19,7 @@ var builder = WebApplication.CreateBuilder(args); Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .Enrich.FromLogContext() - .Enrich.WithProperty("app", "blinks") + .Enrich.WithProperty("app", "chat") .WriteTo.Console() .WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); diff --git a/Chat/Properties/launchSettings.json b/Chat/Properties/launchSettings.json index f1e825d..e002909 100644 --- a/Chat/Properties/launchSettings.json +++ b/Chat/Properties/launchSettings.json @@ -18,7 +18,8 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "https://192.168.0.13:7078;http://192.168.0.13:5094;https://localhost:7078;http://localhost:5094" + "applicationUrl": "https://localhost:7078" + //"applicationUrl": "https://192.168.0.13:7078;http://192.168.0.13:5094;https://localhost:7078;http://localhost:5094" }, "IIS Express": { "commandName": "IISExpress", diff --git a/Chat/Views/Chat/Index.cshtml b/Chat/Views/Chat/Index.cshtml index 2295a17..de06250 100644 --- a/Chat/Views/Chat/Index.cshtml +++ b/Chat/Views/Chat/Index.cshtml @@ -2,29 +2,56 @@ ViewData["Title"] = "Chat"; } -@section Styles{ - +@section Styles { + + }
-@* -
-
Chat
-
- *@
@Html.AntiForgeryToken() +
+ @@ -43,8 +70,7 @@ const id = name + '_Web'; const company = 'Domvs iT'; const baseUrl = window.location.origin; - - var token = ''; + var token = '@ViewBag.Token'; // Aguarda todos os recursos serem carregados window.addEventListener('load', async function () { @@ -52,14 +78,18 @@ const chatMessages = $('#chat-messages'); const messageInput = $('#message-input'); const chatForm = $('#chat-form'); + const fileInput = $('#file-input'); + const selectedFilesInfo = $('#selected-files-info'); + let selectedFiles = []; //token = await autenticar(id, company, name); - token = '@ViewBag.Token'; - // Configuração do Marked - marked.setOptions({ - breaks: true, - gfm: true - }); + //token = '@ViewBag.Token'; + + // // Configuração do Marked + // marked.setOptions({ + // breaks: true, + // gfm: true + // }); const msgInit = `Olá! Eu sou uma IA desenvolvida para atuar internamente na Domvs iT. Você pode: - Fazer perguntas sobre os serviços da Domvs iT @@ -134,58 +164,75 @@ seu código // Exibe mensagem inicial appendMessage(msgInit, false); - $('#send-message').on('click', function (e) { + // Arquivo handling + fileInput.on('change', function (e) { + const files = Array.from(e.target.files); + + if (files.length > 2) { + alert('Por favor, selecione no máximo 2 arquivos PDF/PNG.'); + fileInput.val(''); + selectedFiles = []; + selectedFilesInfo.html(''); + return; + } + + if (files.some(file => file.type !== 'application/pdf' && file.type !== 'image/png')) { + alert('Por favor, selecione apenas arquivos PDF/PNG.'); + fileInput.val(''); + selectedFiles = []; + selectedFilesInfo.html(''); + return; + } + + selectedFiles = files; + selectedFilesInfo.html( + files.map(file => `
${file.name}
`).join('') + ); + }); + + // Send message handling + $('#send-message').on('click', async function (e) { e.preventDefault(); const message = messageInput.val().trim(); if (!message) return; + + const formData = new FormData(); + formData.append('message', message); + formData.append('sessionId', id); + + selectedFiles.forEach((file, index) => { + formData.append(`file${index + 1}`, file); + }); + appendMessage(message); messageInput.val(''); - $.ajax({ - url: baseUrl + '/chat/proxy-response', // Novo endpoint no controller - type: 'GET', - headers: { - "X-CSRF-TOKEN": $('input[name="__RequestVerificationToken"]').val(), - 'Authorization': 'Bearer ' + token - }, - data: { - sessionId: id, - message: message - }, - success: function (response) { - appendMessage(response, false); - }, - error: function (xhr, status, error) { - console.error('Erro na requisição do cliente:', error); - console.error('Status:', xhr.status); - console.error('Response:', xhr.responseText); - appendMessage('Erro ao processar cliente: ' + error, false); - } - }); - }); + fileInput.val(''); + selectedFiles = []; + selectedFilesInfo.html(''); - async function autenticar(userId, company, name) { try { - // Agora fazemos apenas uma requisição para o novo endpoint - const response = await $.ajax({ - url: baseUrl + '/chat/authenticate', - type: 'POST', + const response = await fetch(baseUrl + '/chat/proxy-response', { + method: 'POST', headers: { - "X-CSRF-TOKEN": $('input[name="__RequestVerificationToken"]').val() - }, - contentType: 'application/json', - data: JSON.stringify({ - userId: userId, - company: company, - name: name - }) + "X-CSRF-TOKEN": $('input[name="__RequestVerificationToken"]').val(), + 'Authorization': 'Bearer ' + token, + 'X-Requested-With': 'XMLHttpRequest' + }, + body: formData }); - return response.token; - } catch (error) { - appendMessage('Erro ao processar mensagem: ' + error, false); - throw error; - } - } + if (!response.ok) { + throw new Error('Erro na requisição'); + } + + const responseText = await response.text(); + appendMessage(responseText, false); + } catch (error) { + console.error('Erro na requisição:', error); + appendMessage('Erro ao processar mensagem: ' + error, false); + } + }); + });