chore: add workflows + license

This commit is contained in:
Stevan Freeborn
2025-02-09 13:53:24 -06:00
parent 81eba501d0
commit 79f603c63d
7 changed files with 198 additions and 1 deletions
+2
View File
@@ -0,0 +1,2 @@
appsettings*.json
Dockerfile
+17
View File
@@ -0,0 +1,17 @@
# setup stage
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS setup-stage
WORKDIR /app
COPY *.csproj ./
RUN dotnet restore
COPY . .
# build stage
FROM setup-stage AS build-stage
RUN dotnet publish -c Release -o dist
# production stage
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS production-stage
WORKDIR /app
COPY --from=build-stage /app/dist ./
EXPOSE 8080
ENTRYPOINT ["dotnet", "AltGen.API.dll"]
@@ -4,7 +4,8 @@ class GeminiService(HttpClient httpClient) : IGeminiService
{
const string BaseUri = "https://generativelanguage.googleapis.com/v1beta/models";
const string Method = "generateContent";
const string ModelId = "gemini-1.5-flash";
// TODO: Maybe this is configurable by the user via request
const string ModelId = "gemini-2.0-flash";
const string ApiKeyQueryKey = "key";
// TODO: Use Lazy<T> to load the prompt resource
@@ -52,6 +53,8 @@ class GeminiService(HttpClient httpClient) : IGeminiService
var response = await _httpClient.PostAsJsonAsync(requestUri, request, Options);
var content = await response.Content.ReadAsStringAsync();
// TODO: Propogate the error response from gemini in the exception
// we throw here. Need to figure out how gemini returns errors
if (response.IsSuccessStatusCode is false)
{
throw new AltGenException("Failed to generate alt text.");
+43
View File
@@ -1,3 +1,6 @@
using System.Globalization;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
@@ -7,6 +10,41 @@ builder.Services.AddHttpClient<IGeminiService, GeminiService>();
builder.Services.AddSingleton<IAltTextProviderFactory, AltTextProviderFactory>();
builder.Services.AddKeyedSingleton<IAltTextProvider, GeminiAltTextProvider>(LLMProvider.Gemini);
builder.Services.AddRateLimiter(
static o =>
{
o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
o.OnRejected = static (context, rateLimitInfo) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
var retryDate = DateTime.UtcNow.Add(retryAfter);
context.HttpContext.Response.Headers.Append(
"x-retry-after",
retryDate.ToString("o", CultureInfo.InvariantCulture)
);
}
return ValueTask.CompletedTask;
};
o.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
static ctx => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: ctx.Request.Headers["X-Forwarded-For"].ToString()
?? ctx.Connection.RemoteIpAddress?.ToString()
?? "unknown",
factory: static partition => new FixedWindowRateLimiterOptions()
{
PermitLimit = 10,
QueueLimit = 0,
Window = TimeSpan.FromHours(1)
}
)
);
}
);
var app = builder.Build();
if (app.Environment.IsDevelopment())
@@ -14,6 +52,11 @@ if (app.Environment.IsDevelopment())
app.MapOpenApi();
}
if (app.Environment.IsProduction())
{
app.UseRateLimiter();
}
app.UseHttpsRedirection();
app.UseStatusCodePages();