Private
Public Access
1
0

Run AI classification in background with SignalR notifications

Move the classify-uncategorized endpoint from synchronous to async.
The controller now queues a ClassificationJob to an unbounded channel
and returns 202 Accepted. A new BackgroundService processes jobs and
pushes results (or errors) to the user via SignalR. The frontend
listens for the AiClassificationComplete event and surfaces it in the
notification system.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-10 23:53:14 -05:00
parent c2a4272585
commit 333b21224d
5 changed files with 130 additions and 6 deletions
@@ -1,5 +1,7 @@
using System.Threading.Channels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Purrse.Api.Services;
using Purrse.Core.DTOs;
using Purrse.Core.Interfaces.Services;
using System.Security.Claims;
@@ -12,9 +14,14 @@ namespace Purrse.Api.Controllers;
public class AiCategorizationController : ControllerBase
{
private readonly IAiCategorizationService _aiService;
private readonly Channel<ClassificationJob> _classificationChannel;
private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
public AiCategorizationController(IAiCategorizationService aiService) => _aiService = aiService;
public AiCategorizationController(IAiCategorizationService aiService, Channel<ClassificationJob> classificationChannel)
{
_aiService = aiService;
_classificationChannel = classificationChannel;
}
[HttpGet("settings")]
public async Task<ActionResult<AiCategorizationSettingsResponse>> GetSettings()
@@ -29,6 +36,10 @@ public class AiCategorizationController : ControllerBase
=> Ok(await _aiService.TestConnectionAsync(UserId));
[HttpPost("classify")]
public async Task<ActionResult<ClassifyUncategorizedResponse>> ClassifyUncategorized(ClassifyUncategorizedRequest request)
=> Ok(await _aiService.ClassifyUncategorizedAsync(UserId, request));
public async Task<IActionResult> ClassifyUncategorized(ClassifyUncategorizedRequest request)
{
var job = new ClassificationJob(UserId, request.AccountId, request.StartDate, request.EndDate, request.SearchText, request.Status);
await _classificationChannel.Writer.WriteAsync(job);
return Accepted(new { message = "Classification job queued" });
}
}