using System.Net.Http.Headers; using System.Text; using System.Text.Json; using Api.Requests; using Api.Responses; using Api.Services.Contracts; using Api.Settings; using Microsoft.Extensions.Options; namespace Api.Services; public sealed class RagApiClient : IRagApiClient { private readonly HttpClient _http; private readonly RagApiSettings _settings; private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); public RagApiClient(HttpClient http, IOptions options) { _http = http; _settings = options.Value; _http.BaseAddress = new Uri(_settings.BaseUrl.TrimEnd('/') + "/"); if (!string.IsNullOrWhiteSpace(_settings.InternalApiKey)) { _http.DefaultRequestHeaders.Add("X-Internal-Api-Key", _settings.InternalApiKey); } } public async Task IndexCvPdfAsync(IFormFile file, CancellationToken ct) { using var content = new MultipartFormDataContent(); await using var stream = file.OpenReadStream(); using var fileContent = new StreamContent(stream); fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); content.Add(fileContent, "file", file.FileName); content.Add(new StringContent("cv"), "documentType"); content.Add(new StringContent(file.FileName), "title"); using var response = await _http.PostAsync("api/rag/documents", content, ct); return await ReadJsonAsync(response, ct); } public async Task IndexJobTextAsync(string text, string? url, string? title, CancellationToken ct) { using var content = new MultipartFormDataContent { { new StringContent(text), "text" }, { new StringContent("job"), "documentType" }, { new StringContent(title ?? "Job description"), "title" } }; if (!string.IsNullOrWhiteSpace(url)) content.Add(new StringContent(url), "sourceUrl"); using var response = await _http.PostAsync("api/rag/documents", content, ct); return await ReadJsonAsync(response, ct); } public async Task GetDocumentAsync(string documentId, CancellationToken ct) { using var response = await _http.GetAsync($"api/rag/documents/{Uri.EscapeDataString(documentId)}", ct); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) return null; return await ReadJsonAsync(response, ct); } public async Task SearchAsync(RagSearchRequest request, CancellationToken ct) { using var response = await _http.PostAsync( "api/rag/search", new StringContent(JsonSerializer.Serialize(request, JsonOptions), Encoding.UTF8, "application/json"), ct); return await ReadJsonAsync(response, ct); } private static async Task ReadJsonAsync(HttpResponseMessage response, CancellationToken ct) { var json = await response.Content.ReadAsStringAsync(ct); if (!response.IsSuccessStatusCode) { throw new InvalidOperationException($"RAG API failed: {(int)response.StatusCode} {json}"); } return JsonSerializer.Deserialize(json, JsonOptions) ?? throw new InvalidOperationException("RAG API returned invalid JSON."); } }