feat(ai): DB-only config + runtime provider selection via factory

This commit is contained in:
Chris Chen
2026-06-25 13:23:13 -07:00
parent ece9938bfb
commit 120240ad0c
8 changed files with 132 additions and 57 deletions
@@ -1,6 +1,5 @@
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Options;
using ROLAC.API.Data;
namespace ROLAC.API.Services.Ai;
@@ -12,25 +11,28 @@ namespace ROLAC.API.Services.Ai;
/// </summary>
public sealed class GeminiExpenseAiService : ExpenseAiServiceBase
{
private const string BaseUrl = "https://generativelanguage.googleapis.com/v1beta";
private readonly HttpClient _http;
private readonly GeminiOptions _options;
private readonly IChurchAiConfigProvider _config;
private readonly ILogger<GeminiExpenseAiService> _logger;
public GeminiExpenseAiService(
HttpClient http,
IOptions<GeminiOptions> options,
IChurchAiConfigProvider config,
AppDbContext db,
ILogger<GeminiExpenseAiService> logger)
: base(db)
{
_http = http;
_options = options.Value;
_config = config;
_logger = logger;
}
protected override async Task<ModelAnswer?> CallModelAsync(string prompt, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(_options.ApiKey))
var cfg = await _config.GetAsync(ct);
if (string.IsNullOrWhiteSpace(cfg.GeminiApiKey))
{
_logger.LogWarning("Gemini API key is not configured; expense AI assist is disabled.");
return null;
@@ -63,12 +65,12 @@ public sealed class GeminiExpenseAiService : ExpenseAiServiceBase
},
};
var url = $"{_options.BaseUrl}/models/{_options.Model}:generateContent";
var url = $"{BaseUrl}/models/{cfg.GeminiModel}:generateContent";
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = JsonContent.Create(payload),
};
request.Headers.Add("X-goog-api-key", _options.ApiKey);
request.Headers.Add("X-goog-api-key", cfg.GeminiApiKey);
using var response = await _http.SendAsync(request, ct);
if (!response.IsSuccessStatusCode)