From 1f5f49228b59769e2befd527d75ec331d4c09542 Mon Sep 17 00:00:00 2001 From: Meleki Date: Fri, 6 Mar 2026 15:10:26 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20Krea-Bild-URLs=20auf=20/api/images/file/?= =?UTF-8?q?=20korrigieren,=20Thumbnail-Endpoint=20hinzuf=C3=BCgen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- Endpoints/KreaEndpoint.cs | 79 +++++++++++++++++++++++++++++++++++++++ wwwroot/js/ui.js | 4 +- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/Endpoints/KreaEndpoint.cs b/Endpoints/KreaEndpoint.cs index e490a37..b5e50c3 100644 --- a/Endpoints/KreaEndpoint.cs +++ b/Endpoints/KreaEndpoint.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Threading.Tasks; @@ -5,6 +7,9 @@ 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 { @@ -15,6 +20,7 @@ namespace Gott.Endpoints 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); } @@ -108,5 +114,78 @@ namespace Gott.Endpoints models = KreaService.IMAGE_MODELS.Keys, }); } + + private static readonly HashSet 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); + } } } diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js index 1301b5c..83cc922 100644 --- a/wwwroot/js/ui.js +++ b/wwwroot/js/ui.js @@ -170,8 +170,8 @@ function renderMarkdown(text) { html = html.replace(/\[Krea:\s*([^\]]+)\]/g, function(match, filename) { var fn = filename.trim(); var safeFn = esc(fn); - var thumbUrl = '/api/skills/krea-api/output/' + encodeURIComponent(fn) + '/thumb'; - var fullUrl = '/api/skills/krea-api/output/' + encodeURIComponent(fn); + var thumbUrl = '/api/images/file/' + encodeURIComponent(fn) + '/thumb'; + var fullUrl = '/api/images/file/' + encodeURIComponent(fn); return '' + '' + safeFn + '' + '';