From 38b7d8a9ca360d2428b0d31e11c24ff07f3e8de3 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 15 Jan 2026 22:53:40 -0500 Subject: [PATCH] Feature: Add dynamic model selection to ViewReceipt page Enhance receipt parsing model selection: - Fetch available models from LlamaCpp server dynamically - Show loaded/unloaded status in model dropdown - Persist selected model to appsettings.json - Read default model from AI:ReceiptParsingModel config - Inject LlamaCppVisionClient and IConfiguration dependencies Co-Authored-By: Claude Opus 4.5 --- MoneyMap/Pages/ViewReceipt.cshtml | 27 +++++++++------ MoneyMap/Pages/ViewReceipt.cshtml.cs | 51 +++++++++++++++++++++++++++- MoneyMap/Services/AIReceiptParser.cs | 5 ++- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/MoneyMap/Pages/ViewReceipt.cshtml b/MoneyMap/Pages/ViewReceipt.cshtml index e445ab7..203665d 100644 --- a/MoneyMap/Pages/ViewReceipt.cshtml +++ b/MoneyMap/Pages/ViewReceipt.cshtml @@ -165,20 +165,25 @@
diff --git a/MoneyMap/Pages/ViewReceipt.cshtml.cs b/MoneyMap/Pages/ViewReceipt.cshtml.cs index 9b7cacf..45f8e1c 100644 --- a/MoneyMap/Pages/ViewReceipt.cshtml.cs +++ b/MoneyMap/Pages/ViewReceipt.cshtml.cs @@ -4,6 +4,8 @@ using Microsoft.EntityFrameworkCore; using MoneyMap.Data; using MoneyMap.Models; using MoneyMap.Services; +using System.Text.Json; +using System.Text.Json.Nodes; namespace MoneyMap.Pages { @@ -12,22 +14,33 @@ namespace MoneyMap.Pages private readonly MoneyMapContext _db; private readonly IReceiptManager _receiptManager; private readonly IEnumerable _parsers; + private readonly LlamaCppVisionClient _llamaClient; + private readonly IConfiguration _config; + private readonly IWebHostEnvironment _env; public ViewReceiptModel( MoneyMapContext db, IReceiptManager receiptManager, - IEnumerable parsers) + IEnumerable parsers, + LlamaCppVisionClient llamaClient, + IConfiguration config, + IWebHostEnvironment env) { _db = db; _receiptManager = receiptManager; _parsers = parsers; + _llamaClient = llamaClient; + _config = config; + _env = env; } public Receipt? Receipt { get; set; } public List LineItems { get; set; } = new(); public List ParseLogs { get; set; } = new(); public List AvailableParsers { get; set; } = new(); + public List AvailableModels { get; set; } = new(); public string ReceiptUrl { get; set; } = ""; + public string SelectedModel => _config["AI:ReceiptParsingModel"] ?? "gpt-4o-mini"; [TempData] public string? SuccessMessage { get; set; } @@ -59,6 +72,9 @@ namespace MoneyMap.Pages FullName = p.GetType().Name }).ToList(); + // Get available LlamaCpp models + AvailableModels = await _llamaClient.GetAvailableModelsAsync(); + return Page(); } @@ -94,6 +110,12 @@ namespace MoneyMap.Pages return RedirectToPage(new { id }); } + // Save selected model to config if it changed + if (!string.IsNullOrEmpty(model) && model != SelectedModel) + { + await SaveSelectedModelAsync(model); + } + var result = await selectedParser.ParseReceiptAsync(id, model); if (result.IsSuccess) @@ -108,6 +130,33 @@ namespace MoneyMap.Pages return RedirectToPage(new { id }); } + private async Task SaveSelectedModelAsync(string model) + { + try + { + var appSettingsPath = Path.Combine(_env.ContentRootPath, "appsettings.json"); + var json = await System.IO.File.ReadAllTextAsync(appSettingsPath); + var jsonNode = JsonNode.Parse(json); + + if (jsonNode == null) return; + + // Ensure AI section exists + if (jsonNode["AI"] == null) + { + jsonNode["AI"] = new JsonObject(); + } + + jsonNode["AI"]!["ReceiptParsingModel"] = model; + + var options = new JsonSerializerOptions { WriteIndented = true }; + await System.IO.File.WriteAllTextAsync(appSettingsPath, jsonNode.ToJsonString(options)); + } + catch + { + // Silently fail - not critical if we can't save the preference + } + } + public class ParserOption { public string Name { get; set; } = ""; diff --git a/MoneyMap/Services/AIReceiptParser.cs b/MoneyMap/Services/AIReceiptParser.cs index c2ac467..d569704 100644 --- a/MoneyMap/Services/AIReceiptParser.cs +++ b/MoneyMap/Services/AIReceiptParser.cs @@ -21,6 +21,7 @@ namespace MoneyMap.Services private readonly LlamaCppVisionClient _llamaCppClient; private readonly IMerchantService _merchantService; private readonly IServiceProvider _serviceProvider; + private readonly IConfiguration _configuration; private readonly ILogger _logger; private string? _promptTemplate; @@ -34,6 +35,7 @@ namespace MoneyMap.Services LlamaCppVisionClient llamaCppClient, IMerchantService merchantService, IServiceProvider serviceProvider, + IConfiguration configuration, ILogger logger) { _db = db; @@ -45,6 +47,7 @@ namespace MoneyMap.Services _llamaCppClient = llamaCppClient; _merchantService = merchantService; _serviceProvider = serviceProvider; + _configuration = configuration; _logger = logger; } @@ -57,7 +60,7 @@ namespace MoneyMap.Services if (receipt == null) return ReceiptParseResult.Failure("Receipt not found."); - var selectedModel = model ?? "gpt-4o-mini"; + var selectedModel = model ?? _configuration["AI:ReceiptParsingModel"] ?? "gpt-4o-mini"; var isLlamaCpp = selectedModel.StartsWith("llamacpp:"); var isOllama = selectedModel.StartsWith("ollama:"); var isClaude = selectedModel.StartsWith("claude-");