feat: meter AI usage
CI and Deploy / test (push) Failing after 58s
CI and Deploy / deploy (push) Has been skipped

This commit is contained in:
cesnimda
2026-07-30 22:39:24 +02:00
parent f8466c2ebc
commit c08232b9d7
15 changed files with 2482 additions and 11 deletions
@@ -0,0 +1,48 @@
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace JobTrackerApi.Controllers;
[ApiController]
[Route("api/ai/usage")]
[Authorize(AuthenticationSchemes = "local")]
public sealed class AiUsageController : ControllerBase
{
private readonly UserManager<ApplicationUser> _users;
private readonly JobTrackerContext _db;
public AiUsageController(UserManager<ApplicationUser> users, JobTrackerContext db)
{
_users = users;
_db = db;
}
public sealed record UsagePeriodDto(int Calls, long InputCharacters, long OutputCharacters, long EstimatedTokens);
public sealed record UsageDto(UsagePeriodDto CurrentMonth, UsagePeriodDto AllTime);
[HttpGet]
public async Task<ActionResult<UsageDto>> Get(CancellationToken cancellationToken)
{
var user = await _users.GetUserAsync(User);
if (user is null) return Unauthorized();
var monthStart = new DateTimeOffset(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1, 0, 0, 0, TimeSpan.Zero);
return Ok(new UsageDto(
await SumAsync(_db.AiInteractions.Where(x => x.OwnerUserId == user.Id && x.CreatedAtUtc >= monthStart), cancellationToken),
await SumAsync(_db.AiInteractions.Where(x => x.OwnerUserId == user.Id), cancellationToken)));
}
private static async Task<UsagePeriodDto> SumAsync(IQueryable<AiInteraction> query, CancellationToken cancellationToken)
{
var totals = await query.GroupBy(_ => 1).Select(group => new UsagePeriodDto(
group.Count(),
group.Sum(x => (long)x.InputCharacterCount),
group.Sum(x => (long)x.OutputCharacterCount),
group.Sum(x => (long)x.EstimatedTokenCount))).FirstOrDefaultAsync(cancellationToken);
return totals ?? new UsagePeriodDto(0, 0, 0, 0);
}
}
@@ -26,7 +26,7 @@ public sealed class AiWorkspaceController : ControllerBase
}
public sealed record GenerateRequest(string Module, string? Mode, string? ExtraContext);
public sealed record InteractionDto(int Id, string Module, string? Mode, string Title, string Provider, JsonElement Result, DateTimeOffset CreatedAtUtc);
public sealed record InteractionDto(int Id, string Module, string? Mode, string Title, string Provider, JsonElement Result, int InputCharacterCount, int OutputCharacterCount, int EstimatedTokenCount, DateTimeOffset CreatedAtUtc);
[HttpGet("modules")]
public ActionResult<object> Modules() => Ok(new { modules = _workspace.Modules, provider = ResolveProvider() });
@@ -86,5 +86,5 @@ public sealed class AiWorkspaceController : ControllerBase
private static InteractionDto ToDto(AiInteraction x) => new(
x.Id, x.Module, x.Mode, x.Title, x.Provider,
JsonSerializer.Deserialize<JsonElement>(string.IsNullOrWhiteSpace(x.ResultJson) ? "{}" : x.ResultJson),
x.CreatedAtUtc);
x.InputCharacterCount, x.OutputCharacterCount, x.EstimatedTokenCount, x.CreatedAtUtc);
}