- ui.js: [Krea: datei] URLs von /api/skills/krea-api/output/ auf /api/images/file/ geändert
- KreaEndpoint: GET /api/images/file/{filename}/thumb mit gecachten WebP-Thumbnails (320px)
- Thumbnail-Cache in data/cache/images/, analog zum SkillEndpoint
192 lines
7.1 KiB
C#
192 lines
7.1 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text.Json;
|
||
using System.Threading.Tasks;
|
||
using Gott.Services;
|
||
using Microsoft.AspNetCore.Builder;
|
||
using Microsoft.AspNetCore.Http;
|
||
using Microsoft.AspNetCore.Routing;
|
||
using SixLabors.ImageSharp;
|
||
using SixLabors.ImageSharp.Processing;
|
||
using SixLabors.ImageSharp.Formats.Webp;
|
||
|
||
namespace Gott.Endpoints
|
||
{
|
||
public static class KreaEndpoint
|
||
{
|
||
public static void MapKreaEndpoints(this IEndpointRouteBuilder app)
|
||
{
|
||
app.MapPost("/api/images/generate", HandleGenerate);
|
||
app.MapGet("/api/images", HandleList);
|
||
app.MapGet("/api/images/file/{filename}", HandleFile);
|
||
app.MapGet("/api/images/file/{filename}/thumb", HandleThumb);
|
||
app.MapGet("/api/images/status", HandleStatus);
|
||
}
|
||
|
||
private static async Task HandleGenerate(HttpRequest req, HttpResponse res, KreaService krea)
|
||
{
|
||
if (!krea.IsConfigured)
|
||
{
|
||
res.StatusCode = 400;
|
||
await res.WriteAsJsonAsync(new { error = "Krea API nicht konfiguriert (KreaToken fehlt)" });
|
||
return;
|
||
}
|
||
|
||
JsonElement body;
|
||
try { body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body); }
|
||
catch
|
||
{
|
||
res.StatusCode = 400;
|
||
await res.WriteAsJsonAsync(new { error = "Ungültiger JSON-Body" });
|
||
return;
|
||
}
|
||
|
||
var prompt = body.TryGetProperty("prompt", out var p) ? p.GetString() : null;
|
||
if (string.IsNullOrWhiteSpace(prompt))
|
||
{
|
||
res.StatusCode = 400;
|
||
await res.WriteAsJsonAsync(new { error = "prompt ist erforderlich" });
|
||
return;
|
||
}
|
||
|
||
var model = body.TryGetProperty("model", out var m) ? m.GetString() : "flux";
|
||
var width = body.TryGetProperty("width", out var w) ? w.GetInt32() : 1024;
|
||
var height = body.TryGetProperty("height", out var h) ? h.GetInt32() : 1024;
|
||
var steps = body.TryGetProperty("steps", out var st) ? st.GetInt32() : 25;
|
||
var guidanceScale = body.TryGetProperty("guidanceScale", out var g) ? g.GetDouble() : 3.0;
|
||
var seed = body.TryGetProperty("seed", out var sd) ? sd.GetString() : null;
|
||
var batchSize = body.TryGetProperty("batchSize", out var b) ? b.GetInt32() : 1;
|
||
|
||
try
|
||
{
|
||
var result = await krea.GenerateAsync(prompt, model, width, height, steps, guidanceScale, seed, batchSize);
|
||
await res.WriteAsJsonAsync(result);
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
res.StatusCode = 500;
|
||
await res.WriteAsJsonAsync(new { error = ex.Message });
|
||
}
|
||
}
|
||
|
||
private static async Task HandleList(HttpRequest req, HttpResponse res, KreaService krea)
|
||
{
|
||
var images = krea.ListImages();
|
||
await res.WriteAsJsonAsync(images);
|
||
}
|
||
|
||
private static async Task HandleFile(HttpRequest req, HttpResponse res, KreaService krea, string filename)
|
||
{
|
||
// Sicherheitscheck – kein Path Traversal
|
||
if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
|
||
{
|
||
res.StatusCode = 400;
|
||
return;
|
||
}
|
||
|
||
var path = krea.GetImagePath(filename);
|
||
if (path == null)
|
||
{
|
||
res.StatusCode = 404;
|
||
return;
|
||
}
|
||
|
||
var ext = Path.GetExtension(filename).ToLower();
|
||
var contentType = ext switch
|
||
{
|
||
".png" => "image/png",
|
||
".jpg" => "image/jpeg",
|
||
".jpeg" => "image/jpeg",
|
||
".gif" => "image/gif",
|
||
_ => "image/webp",
|
||
};
|
||
|
||
res.ContentType = contentType;
|
||
await res.SendFileAsync(path);
|
||
}
|
||
|
||
private static async Task HandleStatus(HttpRequest req, HttpResponse res, KreaService krea)
|
||
{
|
||
await res.WriteAsJsonAsync(new
|
||
{
|
||
configured = krea.IsConfigured,
|
||
models = KreaService.IMAGE_MODELS.Keys,
|
||
});
|
||
}
|
||
|
||
private static readonly HashSet<string> ThumbExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"
|
||
};
|
||
|
||
private static async Task HandleThumb(HttpRequest req, HttpResponse res, KreaService krea, string filename)
|
||
{
|
||
if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
|
||
{
|
||
res.StatusCode = 400;
|
||
return;
|
||
}
|
||
|
||
var ext = Path.GetExtension(filename);
|
||
if (!ThumbExtensions.Contains(ext))
|
||
{
|
||
res.StatusCode = 400;
|
||
return;
|
||
}
|
||
|
||
var originalPath = krea.GetImagePath(filename);
|
||
if (originalPath == null)
|
||
{
|
||
res.StatusCode = 404;
|
||
return;
|
||
}
|
||
|
||
// Cache-Verzeichnis: data/cache/images/
|
||
var imagesDir = Path.GetDirectoryName(originalPath);
|
||
var dataDir = Path.GetDirectoryName(imagesDir);
|
||
var cacheDir = Path.Combine(dataDir, "cache", "images");
|
||
Directory.CreateDirectory(cacheDir);
|
||
|
||
var thumbFilename = Path.GetFileNameWithoutExtension(filename) + ".webp";
|
||
var thumbPath = Path.Combine(cacheDir, thumbFilename);
|
||
|
||
if (!File.Exists(thumbPath) || File.GetLastWriteTimeUtc(originalPath) > File.GetLastWriteTimeUtc(thumbPath))
|
||
{
|
||
try
|
||
{
|
||
using var image = await Image.LoadAsync(originalPath);
|
||
var maxSize = 320;
|
||
image.Mutate(x => x.Resize(new ResizeOptions
|
||
{
|
||
Size = new Size(maxSize, maxSize),
|
||
Mode = ResizeMode.Max
|
||
}));
|
||
var encoder = new WebpEncoder { Quality = 75 };
|
||
await image.SaveAsync(thumbPath, encoder);
|
||
}
|
||
catch
|
||
{
|
||
// Fallback: Original ausliefern
|
||
var contentType = ext switch
|
||
{
|
||
".png" => "image/png",
|
||
".jpg" => "image/jpeg",
|
||
".jpeg" => "image/jpeg",
|
||
".gif" => "image/gif",
|
||
_ => "image/webp",
|
||
};
|
||
res.ContentType = contentType;
|
||
res.Headers["Cache-Control"] = "private, max-age=3600";
|
||
await res.SendFileAsync(originalPath);
|
||
return;
|
||
}
|
||
}
|
||
|
||
res.ContentType = "image/webp";
|
||
res.Headers["Cache-Control"] = "private, max-age=86400";
|
||
await res.SendFileAsync(thumbPath);
|
||
}
|
||
}
|
||
}
|