@@ -1,5 +1,6 @@
|
|||||||
using Refit;
|
using Refit;
|
||||||
using Models.Requests;
|
using Models.Requests;
|
||||||
|
using CvMatcher.Models.Responses;
|
||||||
|
|
||||||
namespace Api.Clients.Api.Contracts;
|
namespace Api.Clients.Api.Contracts;
|
||||||
|
|
||||||
@@ -7,8 +8,8 @@ public interface ICvMatcherApi
|
|||||||
{
|
{
|
||||||
[Multipart]
|
[Multipart]
|
||||||
[Post("/api/cv/upload")]
|
[Post("/api/cv/upload")]
|
||||||
Task<HttpResponseMessage> Upload([AliasAs("cv")] StreamPart cv, [AliasAs("gdprConsent")] bool gdprConsent);
|
Task<CvUploadResponse> Upload([AliasAs("cv")] StreamPart cv, [AliasAs("gdprConsent")] bool gdprConsent);
|
||||||
|
|
||||||
[Post("/api/cv/match-job")]
|
[Post("/api/cv/match-job")]
|
||||||
Task<HttpResponseMessage> MatchJob([Body] JobMatchRequest request);
|
Task<JobMatchResponse> MatchJob([Body] JobMatchRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ public sealed class CvMatcherController : ControllerBase
|
|||||||
|
|
||||||
var stream = cv.OpenReadStream();
|
var stream = cv.OpenReadStream();
|
||||||
var part = new Refit.StreamPart(stream, cv.FileName, "application/pdf");
|
var part = new Refit.StreamPart(stream, cv.FileName, "application/pdf");
|
||||||
using var response = await _cvApi.Upload(part, gdprConsent);
|
var res = await _cvApi.Upload(part, gdprConsent);
|
||||||
return await ProxyResponseAsync(response, ct);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -114,9 +114,8 @@ public sealed class CvMatcherController : ControllerBase
|
|||||||
request.CvDocumentId,
|
request.CvDocumentId,
|
||||||
!string.IsNullOrWhiteSpace(request.JobUrl),
|
!string.IsNullOrWhiteSpace(request.JobUrl),
|
||||||
!string.IsNullOrWhiteSpace(request.JobDescription));
|
!string.IsNullOrWhiteSpace(request.JobDescription));
|
||||||
|
var res = await _cvApi.MatchJob(request);
|
||||||
using var response = await _cvApi.MatchJob(request);
|
return Ok(res);
|
||||||
return await ProxyResponseAsync(response, ct);
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -129,15 +128,4 @@ public sealed class CvMatcherController : ControllerBase
|
|||||||
return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API request failed." });
|
return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API request failed." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ContentResult> ProxyResponseAsync(HttpResponseMessage response, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var body = await response.Content.ReadAsStringAsync(ct);
|
|
||||||
return new ContentResult
|
|
||||||
{
|
|
||||||
StatusCode = (int)response.StatusCode,
|
|
||||||
Content = body,
|
|
||||||
ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/json"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ namespace Api.Services.Contracts
|
|||||||
Task SendContactAsync(ContactRequest req, CancellationToken ct);
|
Task SendContactAsync(ContactRequest req, CancellationToken ct);
|
||||||
Task SendSubscribeAsync(SubscribeRequest req, CancellationToken ct);
|
Task SendSubscribeAsync(SubscribeRequest req, CancellationToken ct);
|
||||||
Task SendFileDownloadNotificationAsync(string fileName, string? userIp, CancellationToken ct);
|
Task SendFileDownloadNotificationAsync(string fileName, string? userIp, CancellationToken ct);
|
||||||
|
Task SendMatchAsync(string? explicitTo, string subject, string body, CancellationToken ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,5 +166,10 @@ namespace Api.Services
|
|||||||
await client.SendAsync(message, ct);
|
await client.SendAsync(message, ct);
|
||||||
await client.DisconnectAsync(true, ct);
|
await client.DisconnectAsync(true, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SendMatchAsync(string? explicitTo, string subject, string body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\api-models\api-models.csproj" />
|
<ProjectReference Include="..\api-models\api-models.csproj" />
|
||||||
|
<ProjectReference Include="..\cv-matcher-api-models\cv-matcher-api-models.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -7,6 +7,5 @@
|
|||||||
public string? JobDescription { get; set; }
|
public string? JobDescription { get; set; }
|
||||||
public bool GdprConsent { get; set; }
|
public bool GdprConsent { get; set; }
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
public string? CaptchaToken { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using CvMatcher.Models.Requests;
|
using CvMatcher.Models.Requests;
|
||||||
using Api.Services.Contracts;
|
using Api.Services.Contracts;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using CvMatcher.Models.Responses;
|
||||||
|
|
||||||
namespace Api.Controllers;
|
namespace Api.Controllers;
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ public sealed class CvController : ControllerBase
|
|||||||
|
|
||||||
[HttpPost("upload")]
|
[HttpPost("upload")]
|
||||||
[RequestSizeLimit(10 * 1024 * 1024)]
|
[RequestSizeLimit(10 * 1024 * 1024)]
|
||||||
public async Task<IActionResult> Upload([FromForm(Name = "cv")] IFormFile? cv, [FromForm] bool gdprConsent, CancellationToken ct)
|
public async Task<ActionResult<CvUploadResponse>> Upload([FromForm(Name = "cv")] IFormFile? cv, [FromForm] bool gdprConsent, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -37,14 +38,14 @@ public sealed class CvController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("find-jobs")]
|
[HttpPost("find-jobs")]
|
||||||
public async Task<IActionResult> FindJobs([FromBody] FindJobsRequest request, CancellationToken ct)
|
public async Task<ActionResult<FindJobsResponse>> FindJobs([FromBody] FindJobsRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Find jobs request received. CvDocumentId={CvDocumentId}, TopK={TopK}", request.CvDocumentId, request.TopK);
|
_logger.LogInformation("Find jobs request received. CvDocumentId={CvDocumentId}, TopK={TopK}", request.CvDocumentId, request.TopK);
|
||||||
var result = await _service.FindJobsAsync(request, ct);
|
var result = await _service.FindJobsAsync(request, ct);
|
||||||
_logger.LogInformation("Find jobs completed. CvDocumentId={CvDocumentId}, ResultCount={ResultCount}", request.CvDocumentId, result.Jobs.Count);
|
_logger.LogInformation("Find jobs completed. CvDocumentId={CvDocumentId}, ResultCount={ResultCount}", request.CvDocumentId, result.Jobs.Count);
|
||||||
return Ok(result);
|
return result;
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
@@ -54,7 +55,7 @@ public sealed class CvController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("match-job")]
|
[HttpPost("match-job")]
|
||||||
public async Task<IActionResult> MatchJob([FromBody] MatchJobRequest request, CancellationToken ct)
|
public async Task<ActionResult<JobMatchResponse>> MatchJob([FromBody] MatchJobRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -62,7 +63,7 @@ public sealed class CvController : ControllerBase
|
|||||||
request.CvDocumentId, !string.IsNullOrWhiteSpace(request.JobUrl), !string.IsNullOrWhiteSpace(request.JobDescription), !string.IsNullOrWhiteSpace(request.Email));
|
request.CvDocumentId, !string.IsNullOrWhiteSpace(request.JobUrl), !string.IsNullOrWhiteSpace(request.JobDescription), !string.IsNullOrWhiteSpace(request.Email));
|
||||||
var result = await _service.MatchJobAsync(request, ct);
|
var result = await _service.MatchJobAsync(request, ct);
|
||||||
_logger.LogInformation("Match job completed. CvDocumentId={CvDocumentId}, Score={Score}, Cached={Cached}", request.CvDocumentId, result.Score, result.Cached);
|
_logger.LogInformation("Match job completed. CvDocumentId={CvDocumentId}, Score={Score}, Cached={Cached}", request.CvDocumentId, result.Score, result.Cached);
|
||||||
return Ok(result);
|
return result;
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ try
|
|||||||
?? throw new InvalidOperationException("Connection string 'CvMatcherDb' is missing.")));
|
?? throw new InvalidOperationException("Connection string 'CvMatcherDb' is missing.")));
|
||||||
builder.Services.AddScoped<IMatcherRepository, EfMatcherRepository>();
|
builder.Services.AddScoped<IMatcherRepository, EfMatcherRepository>();
|
||||||
builder.Services.AddScoped<ICvMatcherService, CvMatcherService>();
|
builder.Services.AddScoped<ICvMatcherService, CvMatcherService>();
|
||||||
builder.Services.AddSingleton<IEmailService, EmailService>();
|
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Api.Services.Contracts;
|
|
||||||
|
|
||||||
public interface IEmailService
|
|
||||||
{
|
|
||||||
Task SendMatchAsync(string? explicitTo, string subject, string body, CancellationToken ct);
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ public sealed class CvMatcherService : ICvMatcherService
|
|||||||
private readonly IJobTextExtractor _jobTextExtractor;
|
private readonly IJobTextExtractor _jobTextExtractor;
|
||||||
private readonly IMatcherAiClient _ai;
|
private readonly IMatcherAiClient _ai;
|
||||||
private readonly IMatcherRepository _repository;
|
private readonly IMatcherRepository _repository;
|
||||||
private readonly IEmailService _email;
|
|
||||||
private readonly MatcherSettings _settings;
|
private readonly MatcherSettings _settings;
|
||||||
|
|
||||||
public CvMatcherService(
|
public CvMatcherService(
|
||||||
@@ -24,14 +23,12 @@ public sealed class CvMatcherService : ICvMatcherService
|
|||||||
IJobTextExtractor jobTextExtractor,
|
IJobTextExtractor jobTextExtractor,
|
||||||
IMatcherAiClient ai,
|
IMatcherAiClient ai,
|
||||||
IMatcherRepository repository,
|
IMatcherRepository repository,
|
||||||
IEmailService email,
|
|
||||||
IOptions<MatcherSettings> options)
|
IOptions<MatcherSettings> options)
|
||||||
{
|
{
|
||||||
_rag = rag;
|
_rag = rag;
|
||||||
_jobTextExtractor = jobTextExtractor;
|
_jobTextExtractor = jobTextExtractor;
|
||||||
_ai = ai;
|
_ai = ai;
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_email = email;
|
|
||||||
_settings = options.Value;
|
_settings = options.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,11 +135,11 @@ public sealed class CvMatcherService : ICvMatcherService
|
|||||||
result.Cached = false;
|
result.Cached = false;
|
||||||
await _repository.SaveMatchAsync(cv.Id, job.Id, result, ct);
|
await _repository.SaveMatchAsync(cv.Id, job.Id, result, ct);
|
||||||
|
|
||||||
await _email.SendMatchAsync(
|
//await _email.SendMatchAsync(
|
||||||
email,
|
// email,
|
||||||
$"MyAi.ro CV Match: {result.Score}% - {job.Title}",
|
// $"MyAi.ro CV Match: {result.Score}% - {job.Title}",
|
||||||
BuildEmailBody(cv, job, result),
|
// BuildEmailBody(cv, job, result),
|
||||||
ct);
|
// ct);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -181,24 +178,24 @@ public sealed class CvMatcherService : ICvMatcherService
|
|||||||
|
|
||||||
private static string Limit(string value, int max) => value.Length <= max ? value : value[..max];
|
private static string Limit(string value, int max) => value.Length <= max ? value : value[..max];
|
||||||
|
|
||||||
private static string BuildEmailBody(RagDocumentDetails cv, RagDocumentDetails job, JobMatchResponse result) => $"""
|
//private static string BuildEmailBody(RagDocumentDetails cv, RagDocumentDetails job, JobMatchResponse result) => $"""
|
||||||
CV Matcher result
|
// CV Matcher result
|
||||||
|
|
||||||
CV: {cv.Title}
|
// CV: {cv.Title}
|
||||||
Job: {job.Title}
|
// Job: {job.Title}
|
||||||
Job URL: {job.SourceUrl ?? "N/A"}
|
// Job URL: {job.SourceUrl ?? "N/A"}
|
||||||
Score: {result.Score}%
|
// Score: {result.Score}%
|
||||||
|
|
||||||
Summary:
|
// Summary:
|
||||||
{result.Summary}
|
// {result.Summary}
|
||||||
|
|
||||||
Strengths:
|
// Strengths:
|
||||||
- {string.Join("\n- ", result.Strengths)}
|
// - {string.Join("\n- ", result.Strengths)}
|
||||||
|
|
||||||
Gaps:
|
// Gaps:
|
||||||
- {string.Join("\n- ", result.Gaps)}
|
// - {string.Join("\n- ", result.Gaps)}
|
||||||
|
|
||||||
Recommendations:
|
// Recommendations:
|
||||||
- {string.Join("\n- ", result.Recommendations)}
|
// - {string.Join("\n- ", result.Recommendations)}
|
||||||
""";
|
// """;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
using CvMatcher.Models.Settings;
|
|
||||||
using Api.Services.Contracts;
|
|
||||||
using MailKit.Net.Smtp;
|
|
||||||
using MailKit.Security;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using MimeKit;
|
|
||||||
|
|
||||||
namespace Api.Services;
|
|
||||||
|
|
||||||
public sealed class EmailService : IEmailService
|
|
||||||
{
|
|
||||||
private readonly SmtpSettings _settings;
|
|
||||||
private readonly ILogger<EmailService> _logger;
|
|
||||||
|
|
||||||
public EmailService(IOptions<SmtpSettings> options, ILogger<EmailService> logger)
|
|
||||||
{
|
|
||||||
_settings = options.Value;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendMatchAsync(string? explicitTo, string subject, string body, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var to = !string.IsNullOrWhiteSpace(explicitTo) ? explicitTo : _settings.ToEmail;
|
|
||||||
if (string.IsNullOrWhiteSpace(_settings.Host) || string.IsNullOrWhiteSpace(to))
|
|
||||||
{
|
|
||||||
_logger.LogInformation("SMTP is not configured. Skipping CV matcher email.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = new MimeMessage();
|
|
||||||
message.From.Add(MailboxAddress.Parse(_settings.FromEmail));
|
|
||||||
message.To.Add(MailboxAddress.Parse(to));
|
|
||||||
message.Subject = subject;
|
|
||||||
message.Body = new TextPart("plain") { Text = body };
|
|
||||||
|
|
||||||
using var client = new SmtpClient();
|
|
||||||
var secureSocket = _settings.UseStartTls ? SecureSocketOptions.StartTls : SecureSocketOptions.Auto;
|
|
||||||
await client.ConnectAsync(_settings.Host, _settings.Port, secureSocket, ct);
|
|
||||||
if (!string.IsNullOrWhiteSpace(_settings.Username))
|
|
||||||
{
|
|
||||||
await client.AuthenticateAsync(_settings.Username, _settings.Password, ct);
|
|
||||||
}
|
|
||||||
await client.SendAsync(message, ct);
|
|
||||||
await client.DisconnectAsync(true, ct);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
namespace Rag.Models
|
namespace Rag.Models.Responses
|
||||||
{
|
{
|
||||||
public sealed class RagDocumentDetails
|
public sealed class RagDocumentDetailsResponse
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string DocumentType { get; init; }
|
public required string DocumentType { get; init; }
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Api.Services.Contracts;
|
using Api.Services.Contracts;
|
||||||
using Rag.Models.Requests;
|
using Rag.Models.Requests;
|
||||||
|
using Rag.Models.Responses;
|
||||||
|
|
||||||
namespace Api.Controllers;
|
namespace Api.Controllers;
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ public sealed class RagController : ControllerBase
|
|||||||
|
|
||||||
[HttpPost("documents")]
|
[HttpPost("documents")]
|
||||||
[RequestSizeLimit(10 * 1024 * 1024)]
|
[RequestSizeLimit(10 * 1024 * 1024)]
|
||||||
public async Task<IActionResult> IndexDocument(
|
public async Task<ActionResult<IndexDocumentResponse>> IndexDocument(
|
||||||
[FromForm] IFormFile? file,
|
[FromForm] IFormFile? file,
|
||||||
[FromForm] string? text,
|
[FromForm] string? text,
|
||||||
[FromForm] string? documentType,
|
[FromForm] string? documentType,
|
||||||
@@ -59,7 +60,7 @@ public sealed class RagController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("documents/json")]
|
[HttpPost("documents/json")]
|
||||||
public async Task<IActionResult> IndexJsonDocument([FromBody] IndexDocumentRequest request, CancellationToken ct)
|
public async Task<ActionResult<IndexDocumentResponse>> IndexJsonDocument([FromBody] IndexDocumentRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -78,7 +79,7 @@ public sealed class RagController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("search")]
|
[HttpPost("search")]
|
||||||
public async Task<IActionResult> Search([FromBody] SearchRequest request, CancellationToken ct)
|
public async Task<ActionResult<SearchResponse>> Search([FromBody] SearchRequest request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -96,7 +97,7 @@ public sealed class RagController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("documents/{id}")]
|
[HttpGet("documents/{id}")]
|
||||||
public async Task<IActionResult> GetDocument(string id, CancellationToken ct)
|
public async Task<ActionResult<RagDocumentDetailsResponse>> GetDocument(string id, CancellationToken ct)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Get document request received. DocumentId={DocumentId}", id);
|
_logger.LogInformation("Get document request received. DocumentId={DocumentId}", id);
|
||||||
var document = await _ragService.GetDocumentAsync(id, ct);
|
var document = await _ragService.GetDocumentAsync(id, ct);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Rag.Models;
|
|
||||||
using Rag.Models.Requests;
|
using Rag.Models.Requests;
|
||||||
using Rag.Models.Responses;
|
using Rag.Models.Responses;
|
||||||
|
|
||||||
@@ -9,5 +8,5 @@ public interface IRagService
|
|||||||
Task<IndexDocumentResponse> IndexTextAsync(IndexDocumentRequest request, CancellationToken ct);
|
Task<IndexDocumentResponse> IndexTextAsync(IndexDocumentRequest request, CancellationToken ct);
|
||||||
Task<IndexDocumentResponse> IndexPdfAsync(IFormFile file, string? documentType, string? title, string? sourceUrl, CancellationToken ct);
|
Task<IndexDocumentResponse> IndexPdfAsync(IFormFile file, string? documentType, string? title, string? sourceUrl, CancellationToken ct);
|
||||||
Task<SearchResponse> SearchAsync(SearchRequest request, CancellationToken ct);
|
Task<SearchResponse> SearchAsync(SearchRequest request, CancellationToken ct);
|
||||||
Task<RagDocumentDetails?> GetDocumentAsync(string documentId, CancellationToken ct);
|
Task<RagDocumentDetailsResponse?> GetDocumentAsync(string documentId, CancellationToken ct);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ public sealed class RagService : IRagService
|
|||||||
return new SearchResponse { Results = results };
|
return new SearchResponse { Results = results };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RagDocumentDetails?> GetDocumentAsync(string documentId, CancellationToken ct)
|
public async Task<RagDocumentDetailsResponse?> GetDocumentAsync(string documentId, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var document = await _repository.GetDocumentByIdAsync(documentId, ct);
|
var document = await _repository.GetDocumentByIdAsync(documentId, ct);
|
||||||
return document is null ? null : new RagDocumentDetails
|
return document is null ? null : new RagDocumentDetailsResponse
|
||||||
{
|
{
|
||||||
Id = document.Id,
|
Id = document.Id,
|
||||||
DocumentType = document.DocumentType,
|
DocumentType = document.DocumentType,
|
||||||
|
|||||||
Reference in New Issue
Block a user