using Microsoft.AspNetCore.Mvc; using Api.Services.Contracts; using Rag.Models.Requests; using Rag.Models.Responses; namespace Api.Controllers; [ApiController] [Route("api/rag")] public sealed class RagController : ControllerBase { private readonly IRagService _ragService; private readonly ILogger _logger; public RagController(IRagService ragService, ILogger logger) { _ragService = ragService; _logger = logger; } [HttpPost("documents")] [RequestSizeLimit(10 * 1024 * 1024)] public async Task> IndexDocument( [FromForm] IFormFile? file, [FromForm] string? text, [FromForm] string? documentType, [FromForm] string? title, [FromForm] string? sourceUrl, CancellationToken ct) { try { _logger.LogInformation("Index document request received. HasFile={HasFile}, DocumentType={DocumentType}, Title={Title}, SourceUrl={SourceUrl}", file is not null, documentType, title, sourceUrl); if (file is not null) { var result = await _ragService.IndexPdfAsync(file, documentType, title, sourceUrl, ct); _logger.LogInformation("Indexed PDF document. DocumentId={DocumentId}, DocumentType={DocumentType}, Chunks={Chunks}, Cached={Cached}", result.DocumentId, result.DocumentType, result.Chunks, result.Cached); return Ok(result); } var textResult = await _ragService.IndexTextAsync(new IndexDocumentRequest { Text = text, DocumentType = documentType, Title = title, SourceUrl = sourceUrl }, ct); _logger.LogInformation("Indexed text document. DocumentId={DocumentId}, DocumentType={DocumentType}, Chunks={Chunks}, Cached={Cached}", textResult.DocumentId, textResult.DocumentType, textResult.Chunks, textResult.Cached); return Ok(textResult); } catch (InvalidOperationException ex) { _logger.LogWarning(ex, "Invalid document indexing request."); return BadRequest(new { error = ex.Message }); } } [HttpPost("documents/json")] public async Task> IndexJsonDocument([FromBody] IndexDocumentRequest request, CancellationToken ct) { try { _logger.LogInformation("JSON document indexing request received. DocumentType={DocumentType}, Title={Title}, SourceUrl={SourceUrl}", request.DocumentType, request.Title, request.SourceUrl); var result = await _ragService.IndexTextAsync(request, ct); _logger.LogInformation("Indexed JSON document. DocumentId={DocumentId}, DocumentType={DocumentType}, Chunks={Chunks}, Cached={Cached}", result.DocumentId, result.DocumentType, result.Chunks, result.Cached); return Ok(result); } catch (InvalidOperationException ex) { _logger.LogWarning(ex, "Invalid JSON document indexing request."); return BadRequest(new { error = ex.Message }); } } [HttpPost("search")] public async Task> Search([FromBody] SearchRequest request, CancellationToken ct) { try { _logger.LogInformation("Semantic search request received. TargetTypes={TargetTypes}, TopK={TopK}", string.Join(',', request.TargetDocumentTypes ?? System.Array.Empty()), request.TopK); var result = await _ragService.SearchAsync(request, ct); _logger.LogInformation("Semantic search completed. ResultCount={ResultCount}", result.Results.Count); return Ok(result); } catch (InvalidOperationException ex) { _logger.LogWarning(ex, "Invalid semantic search request."); return BadRequest(new { error = ex.Message }); } } [HttpGet("documents/{id}")] public async Task> GetDocument(string id, CancellationToken ct) { _logger.LogInformation("Get document request received. DocumentId={DocumentId}", id); var document = await _ragService.GetDocumentAsync(id, ct); if (document is null) { _logger.LogWarning("Document not found. DocumentId={DocumentId}", id); return NotFound(new { error = "Document not found." }); } return Ok(document); } }