Compare commits

...

3 Commits

Author SHA1 Message Date
Ricardo Carneiro
393f40fa41 fix: ajustes de layout 2025-06-09 23:11:56 -03:00
Ricardo Carneiro
653de55fb4 feat: Versão com comparação de imagens 2025-02-19 09:32:52 -03:00
Ricardo Carneiro
93ce4db4d7 Chat funcional com login Microsoft. 2025-01-31 18:19:35 -03:00
15 changed files with 829 additions and 116 deletions

View File

@ -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;
}
}
}

37
Chat.sln Normal file
View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatMvc", "Chat\ChatMvc.csproj", "{E4FE7FF1-A6A7-4A40-A940-E32C0679FFB0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chat.Infra", "Chat.Infra\Chat.Infra.csproj", "{FE24AD1A-B5A7-4CD4-A4AF-B0E4DE3A0E53}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chat.Domain", "Chat.Domain\Chat.Domain.csproj", "{C3E28CA0-1BD6-4E09-ACC7-D7E32096F027}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E4FE7FF1-A6A7-4A40-A940-E32C0679FFB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4FE7FF1-A6A7-4A40-A940-E32C0679FFB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4FE7FF1-A6A7-4A40-A940-E32C0679FFB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4FE7FF1-A6A7-4A40-A940-E32C0679FFB0}.Release|Any CPU.Build.0 = Release|Any CPU
{FE24AD1A-B5A7-4CD4-A4AF-B0E4DE3A0E53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE24AD1A-B5A7-4CD4-A4AF-B0E4DE3A0E53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE24AD1A-B5A7-4CD4-A4AF-B0E4DE3A0E53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE24AD1A-B5A7-4CD4-A4AF-B0E4DE3A0E53}.Release|Any CPU.Build.0 = Release|Any CPU
{C3E28CA0-1BD6-4E09-ACC7-D7E32096F027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3E28CA0-1BD6-4E09-ACC7-D7E32096F027}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3E28CA0-1BD6-4E09-ACC7-D7E32096F027}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3E28CA0-1BD6-4E09-ACC7-D7E32096F027}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EC4AB36F-49CB-4819-ACA4-3A14CB1D9B50}
EndGlobalSection
EndGlobal

View File

@ -26,6 +26,7 @@
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.0" /> <PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.0" />
<PackageReference Include="Stripe.net" Version="45.13.0" /> <PackageReference Include="Stripe.net" Version="45.13.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -36,6 +37,10 @@
<None Include="wwwroot\js\notif.js" /> <None Include="wwwroot\js\notif.js" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Chat.Domain\Chat.Domain.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Resource.Designer.cs"> <Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>

View File

@ -7,6 +7,10 @@ using System.Text.Json;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using System.Linq;
using Microsoft.AspNetCore.Authentication;
using Chat.Domain.Tools;
using Newtonsoft.Json.Linq;
namespace ChatMvc.Controllers namespace ChatMvc.Controllers
{ {
@ -27,12 +31,14 @@ namespace ChatMvc.Controllers
public IActionResult Index() public IActionResult Index()
{ {
var tokens = _antiforgery.GetAndStoreTokens(HttpContext); var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
var token = HttpContext.GetTokenAsync("access_token").Result;
ViewBag.Token = token;
return View(); return View();
} }
[HttpGet("chat/proxy-response")] [HttpPost("chat/proxy-response")]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<IActionResult> ProxyResponse(string sessionId, string message) public async Task<IActionResult> ProxyResponse([FromForm] string sessionId, [FromForm] string message, [FromForm] IFormFile file1 = null, [FromForm] IFormFile file2 = null)
{ {
try try
{ {
@ -41,20 +47,60 @@ namespace ChatMvc.Controllers
return BadRequest("Requisição inválida"); return BadRequest("Requisição inválida");
} }
var client = _httpClientFactory.CreateClient(); var userId = User.Claims.FirstOrDefault(f => f.Type == "UserId")?.Value;
var baseUrl = _configuration["ExternalApiBaseUrl"]; var name = User.Claims.FirstOrDefault(f => f.Type == "FirstName")?.Value;
var token = Request.Headers["Authorization"].ToString(); 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();
client.DefaultRequestHeaders.Authorization = client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", "")); new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", ""));
var response = await client.GetAsync( var baseUrl = _configuration["ExternalApiBaseUrl"];
$"{baseUrl}/chat/response?sessionId={sessionId}&message={message}");
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<string, string>("sessionId", sessionId),
new KeyValuePair<string, string>("message", message)
});
var response = await client.PostAsync($"{baseUrl}/chat/response", chatContent);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync(); var responseContent = await response.Content.ReadAsStringAsync();
return Ok(content); return Ok(responseContent);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -62,6 +108,112 @@ namespace ChatMvc.Controllers
} }
} }
private async Task<string> 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<dynamic>(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<string, string>("sessionId", sessionId),
new KeyValuePair<string, string>("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<string> 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")] [HttpPost("chat/authenticate")]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<IActionResult> Authenticate([FromBody] AuthenticateRequest request) public async Task<IActionResult> Authenticate([FromBody] AuthenticateRequest request)
@ -117,6 +269,53 @@ namespace ChatMvc.Controllers
return BadRequest($"Erro na autenticação: {ex.Message}"); 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 public class AuthenticateRequest

View File

@ -0,0 +1,102 @@
using ChatMvc.Managers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Net.Http.Headers;
using static System.Net.Mime.MediaTypeNames;
namespace ChatMvc.Controllers
{
[Authorize]
public class DocumentsController : Controller
{
private readonly HttpClient _httpClient;
private readonly TokenManager _tokenManager;
public DocumentsController(TokenManager tokenManager)
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri("http://localhost:5020/");
this._tokenManager = tokenManager;
}
public async Task<IActionResult> 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 ", ""));
var response = await _httpClient.GetAsync("Chat/texts");
if (response.IsSuccessStatusCode)
{
var texts = await response.Content.ReadFromJsonAsync<IEnumerable<TextResponse>>();
return View(texts);
}
return View(new List<TextResponse>());
}
public IActionResult New()
{
return View();
}
public async Task<IActionResult> Edit(string id)
{
var token = User.Claims.FirstOrDefault(f => f.Type == "TokenExternal").Value;
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", ""));
var response = await _httpClient.GetAsync(string.Format("chat/texts/id/{0}", id));
if (response.IsSuccessStatusCode)
{
var text = await response.Content.ReadFromJsonAsync<TextResponse>();
var request = new TextRequest()
{
Id = text.Id,
Title = text.Title,
Content = text.Content
};
return View("New", request);
}
return View("New");
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Save([FromForm] TextRequest request)
{
var token = User.Claims.FirstOrDefault(f => f.Type == "TokenExternal").Value;
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token.Replace("Bearer ", ""));
var response = await _httpClient.PostAsJsonAsync("chat/savetext", request);
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
return View("Novo", request);
}
}
public class TextResponse
{
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
}
public class TextRequest
{
public string? Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
}
}

View File

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Stripe; using Stripe;
using ChatMvc.Managers;
namespace ChatMvc.Controllers namespace ChatMvc.Controllers
{ {
@ -12,12 +13,14 @@ namespace ChatMvc.Controllers
{ {
private readonly ILogger<LoginController> logger; private readonly ILogger<LoginController> logger;
private readonly IHttpClientFactory httpClientFactory; private readonly IHttpClientFactory httpClientFactory;
private readonly TokenManager tokenManager;
private readonly StripeClient _stripeClient; private readonly StripeClient _stripeClient;
public LoginController(ILogger<LoginController> logger, IHttpClientFactory httpClientFactory) public LoginController(ILogger<LoginController> logger, IHttpClientFactory httpClientFactory, TokenManager tokenManager)
{ {
this.logger = logger; this.logger = logger;
this.httpClientFactory = httpClientFactory; this.httpClientFactory = httpClientFactory;
this.tokenManager = tokenManager;
} }
public IActionResult Index() public IActionResult Index()
@ -55,12 +58,17 @@ namespace ChatMvc.Controllers
if (HttpContext.User.FindFirst(ClaimTypes.GivenName) != null) if (HttpContext.User.FindFirst(ClaimTypes.GivenName) != null)
{ {
var token = await tokenManager.GetToken(emailExist, "Domvs iT", HttpContext.User.FindFirst(ClaimTypes.GivenName).Value);
claims = new List<Claim> claims = new List<Claim>
{ {
new Claim(ClaimTypes.Email, emailExist), new Claim(ClaimTypes.Email, emailExist),
new Claim("FirstName", HttpContext.User.FindFirst(ClaimTypes.GivenName).Value), new Claim("FirstName", HttpContext.User.FindFirst(ClaimTypes.GivenName).Value),
new Claim("FullName", HttpContext.User.FindFirst(ClaimTypes.GivenName).Value + " " + HttpContext.User.FindFirst(ClaimTypes.Surname).Value), new Claim("FullName", HttpContext.User.FindFirst(ClaimTypes.GivenName).Value + " " + HttpContext.User.FindFirst(ClaimTypes.Surname).Value),
new Claim(ClaimTypes.Role, "User"), new Claim("CompanyName", "Domvs iT"),
new Claim("UserId", emailExist),
new Claim("TokenExternal", token),
new Claim(ClaimTypes.Role, "User"),
}; };
} }
else if (HttpContext.User.FindFirst(ClaimTypes.Name)!=null) else if (HttpContext.User.FindFirst(ClaimTypes.Name)!=null)
@ -68,12 +76,17 @@ namespace ChatMvc.Controllers
var name = HttpContext.User.FindFirst(ClaimTypes.Name).Value; var name = HttpContext.User.FindFirst(ClaimTypes.Name).Value;
var firstName = name.Split(' ')[0]; var firstName = name.Split(' ')[0];
var token = await tokenManager.GetToken(emailExist, "Domvs iT", firstName);
claims = new List<Claim> claims = new List<Claim>
{ {
new Claim(ClaimTypes.Email, emailExist), new Claim(ClaimTypes.Email, emailExist),
new Claim("FirstName", firstName), new Claim("FirstName", firstName),
new Claim("FullName", name), new Claim("FullName", name),
new Claim(ClaimTypes.Role, "User"), new Claim("UserId", emailExist),
new Claim("CompanyName", "Domvs iT"),
new Claim("TokenExternal", token),
new Claim(ClaimTypes.Role, "User"),
}; };
} }

View File

@ -0,0 +1,121 @@
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
{
public class TokenManager
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
public TokenManager(IHttpClientFactory httpClientFactory, IConfiguration configuration)
{
this._httpClientFactory = httpClientFactory;
this._configuration = configuration;
}
public async Task<string> GetToken(string userId, string company, string name)
{
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<NewClientResponse>(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<TokenResponse>(tokenContent);
return tokenResult.Token;
}
public async Task<string> 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<NewClientResponse>(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<TokenResponse>(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;
}
}
}

View File

@ -1,4 +1,5 @@
using ChatMvc.LogConfig; using ChatMvc.LogConfig;
using ChatMvc.Managers;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.MicrosoftAccount; using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
@ -18,7 +19,7 @@ var builder = WebApplication.CreateBuilder(args);
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information() .MinimumLevel.Information()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.WithProperty("app", "blinks") .Enrich.WithProperty("app", "chat")
.WriteTo.Console() .WriteTo.Console()
.WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day) .WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger(); .CreateLogger();
@ -37,9 +38,12 @@ options =>
}) })
.AddCookie(options => .AddCookie(options =>
{ {
options.ExpireTimeSpan = TimeSpan.FromMinutes(20); //options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true; //options.SlidingExpiration = true;
options.AccessDeniedPath = "/Forbidden/"; options.AccessDeniedPath = "/Forbidden/";
options.Cookie.Name = ".AspNet.SharedCookie";
options.ExpireTimeSpan = TimeSpan.FromDays(30); // Define o tempo de expiração
options.SlidingExpiration = true; // Renova o cookie a cada acesso
}) })
.AddGoogle(googleOptions => .AddGoogle(googleOptions =>
{ {
@ -51,8 +55,7 @@ options =>
microsoftOptions.ClientId = config.GetSection("Microsoft_ClientId").Value; microsoftOptions.ClientId = config.GetSection("Microsoft_ClientId").Value;
//microsoftOptions.ClientSecret = "2a7cb1bd-037a-49fa-9e5e-2b2655431af9"; //microsoftOptions.ClientSecret = "2a7cb1bd-037a-49fa-9e5e-2b2655431af9";
microsoftOptions.ClientSecret = config.GetSection("Microsoft_ClientSecret").Value; microsoftOptions.ClientSecret = config.GetSection("Microsoft_ClientSecret").Value;
}) });
;
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
@ -91,6 +94,8 @@ builder.Services.AddAntiforgery(options =>
options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
}); });
builder.Services.AddScoped<TokenManager>();
var app = builder.Build(); var app = builder.Build();
var locOptions = app.Services.GetService<IOptions<RequestLocalizationOptions>>(); var locOptions = app.Services.GetService<IOptions<RequestLocalizationOptions>>();

View File

@ -11,12 +11,15 @@
}, },
"https": { "https": {
"commandName": "Project", "commandName": "Project",
"windowsAuthentication": false,
"anonymousAuthentication": true,
"launchBrowser": true, "launchBrowser": true,
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },
"dotnetRunMessages": true, "dotnetRunMessages": true,
"applicationUrl": "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": { "IIS Express": {
"commandName": "IISExpress", "commandName": "IISExpress",

View File

@ -2,29 +2,67 @@
ViewData["Title"] = "Chat"; ViewData["Title"] = "Chat";
} }
@section Styles{ @section Styles {
<link href="~/css/chat_index.css" rel="stylesheet" /> <link href="~/css/chat_index.css" rel="stylesheet" />
<style>
.file-upload-button {
position: relative;
overflow: hidden;
}
.file-upload-button input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
cursor: pointer;
display: block;
}
.selected-files {
font-size: 0.8rem;
margin-top: 0.5rem;
color: #666;
}
.message-input {
resize: none;
transition: all 0.3s ease;
min-height: 40px;
max-height: 200px;
}
.message-input:focus {
min-height: 60px;
}
</style>
} }
<div class="container-fluid bg-light"> <div class="container-fluid bg-light main-content">
<div class="row h-100"> <div class="row h-100">
<div class="col-md-10 mx-auto"> <div class="col-md-12 mx-auto">
<div class="card shadow-sm my-4" style="background-color: #f8fff8;"> <div class="card shadow-sm my-4" style="background-color: #f8fff8;">
@*
<div class="card-header bg-success text-white">
<h5 class="mb-0">Chat</h5>
</div>
*@
<div class="card-body"> <div class="card-body">
<div id="chat-messages" class="mb-4" style="height: 350px; overflow-y: auto;"> <div id="chat-messages" class="mb-4" style="height: 350px; overflow-y: auto;">
<!-- Mensagens serão inseridas aqui --> <!-- Mensagens serão inseridas aqui -->
</div> </div>
<form id="chat-form" class="mt-3"> <form id="chat-form" class="mt-3">
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
<div class="selected-files" id="selected-files-info"></div>
<div class="input-group"> <div class="input-group">
<textarea id="message-input" class="form-control" <textarea id="message-input" class="form-control"
placeholder="Digite sua mensagem... Use **texto** para negrito" placeholder="Digite sua mensagem... Use **texto** para negrito"
rows="3" required></textarea> rows="1" required></textarea>
<button type="button" class="btn btn-outline-secondary file-upload-button">
<i class="bi bi-paperclip"></i>
<input type="file" id="file-input" accept="application/pdf,image/png" multiple>
</button>
<button type="button" class="btn btn-success" id="send-message"> <button type="button" class="btn btn-success" id="send-message">
Enviar Enviar
</button> </button>
@ -43,8 +81,7 @@
const id = name + '_Web'; const id = name + '_Web';
const company = 'Domvs iT'; const company = 'Domvs iT';
const baseUrl = window.location.origin; const baseUrl = window.location.origin;
var token = '@ViewBag.Token';
var token = '';
// Aguarda todos os recursos serem carregados // Aguarda todos os recursos serem carregados
window.addEventListener('load', async function () { window.addEventListener('load', async function () {
@ -52,24 +89,28 @@
const chatMessages = $('#chat-messages'); const chatMessages = $('#chat-messages');
const messageInput = $('#message-input'); const messageInput = $('#message-input');
const chatForm = $('#chat-form'); const chatForm = $('#chat-form');
const fileInput = $('#file-input');
const selectedFilesInfo = $('#selected-files-info');
let selectedFiles = [];
token = await autenticar(id, company, name); //token = await autenticar(id, company, name);
//token = '@ViewBag.Token';
// Configuração do Marked // // Configuração do Marked
marked.setOptions({ // marked.setOptions({
breaks: true, // breaks: true,
gfm: true // gfm: true
}); // });
const msgInit = `Olá! Eu sou uma IA desenvolvida para atuar internamente na Domvs iT. Você pode: 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 - Fazer perguntas sobre os serviços da Domvs iT
- Abrir uma solicitação ao RH no Pipefy - Abrir uma solicitação ao RH no Pipefy
- Ou simplesmente perguntar qualquer coisa. - Ou simplesmente perguntar qualquer coisa.
Use **negrito** para destacar texto importante. Use **negrito** para destacar texto importante.
Para código, use \\\`\\\`\\\`linguagem Para código, use \\\`\\\`\\\`linguagem
seu código seu código
\\\`\\\`\\\``; \\\`\\\`\\\``;
function appendMessage(message, isUser = true) { function appendMessage(message, isUser = true) {
const messageDiv = $('<div>').addClass('chat-message ' + (isUser ? 'user-message' : 'bot-message')); const messageDiv = $('<div>').addClass('chat-message ' + (isUser ? 'user-message' : 'bot-message'));
@ -134,58 +175,80 @@ seu código
// Exibe mensagem inicial // Exibe mensagem inicial
appendMessage(msgInit, false); 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 => `<div>${file.name}</div>`).join('')
);
});
$("#message-input").keypress(function (e) {
if (e.which === 13) {
$("#send-message").click();
}
});
$('#send-message').on('click', async function (e) {
e.preventDefault(); e.preventDefault();
const message = messageInput.val().trim(); const message = messageInput.val().trim();
if (!message) return; 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); appendMessage(message);
messageInput.val(''); messageInput.val('');
$.ajax({ fileInput.val('');
url: baseUrl + '/chat/proxy-response', // Novo endpoint no controller selectedFiles = [];
type: 'GET', selectedFilesInfo.html('');
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);
}
});
});
async function autenticar(userId, company, name) {
try { try {
// Agora fazemos apenas uma requisição para o novo endpoint const response = await fetch(baseUrl + '/chat/proxy-response', {
const response = await $.ajax({ method: 'POST',
url: baseUrl + '/chat/authenticate',
type: 'POST',
headers: { headers: {
"X-CSRF-TOKEN": $('input[name="__RequestVerificationToken"]').val() "X-CSRF-TOKEN": $('input[name="__RequestVerificationToken"]').val(),
}, 'Authorization': 'Bearer ' + token,
contentType: 'application/json', 'X-Requested-With': 'XMLHttpRequest'
data: JSON.stringify({ },
userId: userId, body: formData
company: company,
name: name
})
}); });
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);
}
});
}); });
</script> </script>

View File

@ -0,0 +1,50 @@
@using ChatMvc.Controllers
@model IEnumerable<TextResponse>
<div class="container">
<div class="card shadow-lg mt-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="card-title">Documentos</h2>
<a href="@Url.Action("New", "Documents")" class="btn btn-primary">
<i class="fas fa-plus mr-2"></i>Novo
</a>
</div>
<table class="table">
<thead>
<tr>
<th>Título</th>
<th>Conteúdo</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>@item.Title</td>
<td>@(item.Content?.Length > 80 ? item.Content.Substring(0, 80) + "..." : item.Content)</td>
<td>
<a href="@Url.Action("Edit", "Documents", new { id = item.Id })" class="btn btn-primary btn-sm">
<i class="fas fa-edit"></i> Editar
</a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
@section Styles {
<style>
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.card {
border: none;
border-radius: 15px;
}
</style>
}

View File

@ -0,0 +1,31 @@
@using ChatMvc.Controllers
@model TextRequest
<div class="container">
<div class="card shadow-lg mt-4">
<div class="card-body">
<h2 class="card-title mb-4 text-center">Novo Texto</h2>
@using (Html.BeginForm("Save", "Documents", FormMethod.Post))
{
@Html.AntiForgeryToken()
@if (Model!=null && Model.Id != null)
{
@Html.HiddenFor(m => m.Id);
}
<div class="form-group">
@Html.LabelFor(m => m.Title, "Título")
@Html.TextBoxFor(m => m.Title, new { @class = "form-control" })
</div>
<div class="form-group">
@Html.LabelFor(m => m.Content, "Conteúdo")
@Html.TextAreaFor(m => m.Content, new { @class = "form-control", rows = "5" })
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary">Salvar</button>
</div>
}
</div>
</div>
</div>

View File

@ -16,7 +16,9 @@
#wrapper { #wrapper {
opacity: 1; opacity: 1;
transition: opacity 0.3s ease-in-out; transition: opacity 0.3s ease-in-out;
display: block; flex: 1 0 auto;
display: flex;
flex-direction: column;
} }
#wrapper.fade-out { #wrapper.fade-out {
@ -50,8 +52,42 @@
100% { transform: rotate(360deg); } 100% { transform: rotate(360deg); }
} }
html, body {
height: 100%;
margin: 0;
}
body { body {
display: block; display: flex;
flex-direction: column;
min-height: 100vh;
}
.container, .container-fluid {
flex: 1 0 auto;
display: flex;
flex-direction: column;
padding: 1rem;
}
.main-content {
flex: 1 0 auto;
display: flex;
flex-direction: column;
}
.card {
flex: 1 0 auto;
display: flex;
flex-direction: column;
margin: 1rem 0;
}
.card-body {
flex: 1 0 auto;
display: flex;
flex-direction: column;
} }
</style> </style>
@ -61,8 +97,11 @@
<partial name="_Busy" /> <partial name="_Busy" />
<div id="wrapper"> <div id="wrapper">
<nav id="nav-bar" class="navbar navbar-expand-lg navbar-dark"> <nav id="nav-bar" class="navbar navbar-expand-lg navbar-dark">
<!-- Seu código de navegação existente --> <!-- Substituir esta linha no _layout.cshtml -->
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Chat IA</a> <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">
<img src="~/img/logo.png" alt="DOMVS iT" height="30" class="d-inline-block align-top mr-2">
Chat IA
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@ -79,6 +118,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-white" asp-area="" asp-controller="Chat" asp-action="Index">Chat</a> <a class="nav-link text-white" asp-area="" asp-controller="Chat" asp-action="Index">Chat</a>
</li> </li>
<li class="nav-item">
<a class="nav-link text-white" asp-area="" asp-controller="Documents" asp-action="Index">Documentos</a>
</li>
} }
</ul> </ul>
@if (User!=null && User.Identity!=null && !User.Identity.IsAuthenticated) @if (User!=null && User.Identity!=null && !User.Identity.IsAuthenticated)
@ -111,8 +153,8 @@
@RenderBody() @RenderBody()
</div> </div>
<footer class="border-top footer text-muted"> <footer class="border-top footer text-muted ">
<div class="container"> <div>
&copy; 2024 - Chat IA - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> &copy; 2024 - Chat IA - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div> </div>
</footer> </footer>

View File

@ -1,28 +1,27 @@
 /* Color palette variables */
/* Color palette variables */
:root { :root {
--primary-green: #2E7D32; /* Darker green for headers */ --primary-blue: #000722; /* Cor base - azul escuro */
--secondary-green: #43A047; /* Slightly lighter green for interactive elements */ --secondary-blue: #0A1D4C; /* Azul um pouco mais claro para elementos interativos */
--light-green: #C8E6C9; /* Very light green for backgrounds */ --light-blue: #C7D2E9; /* Azul bem claro para backgrounds */
--hover-green: #1B5E20; /* Darker green for hover states */ --hover-blue: #010B3B; /* Azul mais escuro para hover states */
--accent-green: #81C784; /* Medium green for accents */ --accent-blue: #2D4275; /* Azul médio para acentos */
--bg-light: #F1F8E9; /* Super light green for main background */ --bg-light: #EFF2F9; /* Azul super claro para background principal */
} }
/* Navbar styling */ /* Navbar styling */
#nav-bar { #nav-bar {
background-color: var(--primary-green) !important; background-color: var(--primary-blue) !important;
} }
/* Button primary override */ /* Button primary override */
.btn-primary, .btn-success { .btn-primary, .btn-success {
background-color: var(--secondary-green) !important; background-color: var(--secondary-blue) !important;
border-color: var(--secondary-green) !important; border-color: var(--secondary-blue) !important;
} }
.btn-primary:hover, .btn-success:hover { .btn-primary:hover, .btn-success:hover {
background-color: var(--hover-green) !important; background-color: var(--hover-blue) !important;
border-color: var(--hover-green) !important; border-color: var(--hover-blue) !important;
} }
/* Chat card styling */ /* Chat card styling */
@ -32,36 +31,37 @@
} }
.card-header { .card-header {
background-color: var(--primary-green) !important; background-color: var(--primary-blue) !important;
color: white !important;
} }
/* Message bubbles */ /* Message bubbles */
.chat-message-user { .chat-message-user {
background-color: var(--secondary-green) !important; background-color: var(--secondary-blue) !important;
color: white; color: white;
border-radius: 15px 15px 0 15px !important; border-radius: 15px 15px 0 15px !important;
} }
.chat-message-bot { .chat-message-bot {
background-color: white !important; background-color: white !important;
border: 1px solid var(--light-green) !important; border: 1px solid var(--light-blue) !important;
color: #333; color: #333;
border-radius: 15px 15px 15px 0 !important; border-radius: 15px 15px 15px 0 !important;
} }
/* Input group styling */ /* Input group styling */
.input-group .form-control { .input-group .form-control {
border: 1px solid var(--light-green); border: 1px solid var(--light-blue);
} }
.input-group .form-control:focus { .input-group .form-control:focus {
border-color: var(--accent-green); border-color: var(--accent-blue);
box-shadow: 0 0 0 0.2rem rgba(67, 160, 71, 0.25); box-shadow: 0 0 0 0.2rem rgba(10, 29, 76, 0.25);
} }
/* Navbar active state */ /* Navbar active state */
.navbar .navbar-collapse ul.navbar-nav .nav-item.active { .navbar .navbar-collapse ul.navbar-nav .nav-item.active {
background-color: var(--accent-green) !important; background-color: var(--accent-blue) !important;
} }
/* Custom scrollbar for chat messages */ /* Custom scrollbar for chat messages */
@ -74,10 +74,10 @@
} }
#chat-messages::-webkit-scrollbar-thumb { #chat-messages::-webkit-scrollbar-thumb {
background: var(--accent-green); background: var(--accent-blue);
border-radius: 4px; border-radius: 4px;
} }
#chat-messages::-webkit-scrollbar-thumb:hover { #chat-messages::-webkit-scrollbar-thumb:hover {
background: var(--secondary-green); background: var(--secondary-blue);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 16 KiB