ChatContext: 3 identische Context-Klassen (PreparerContext, ReviewContext, FinalizerContext) zu einer SubAgentContext-Klasse zusammengefasst. ChatService: 3x40 Zeilen Sub-Agent-Setup durch BuildSubAgentContext() ersetzt. AnthropicService: 5 Hilfsmethoden extrahiert – ExtractWebSearchSources(), TruncateToolResult(), AddToolsToPayload(), ParseServerToolUse(), TrackUsage(). WorkspaceEndpoint: 5 Session/Agent-Endpoint-Paare durch generische Core-Handler mit basePath-Parameter ersetzt. Ungenutzte rmdir-Endpoints entfernt. AgentEndpoint: Doppeltes Agent-DTO-Mapping durch MapAgentList() ersetzt. SessionEndpoint: Doppelte Session/Agent-Auflösung durch ResolvePromptContext() ersetzt. DatabaseService: Toten Code entfernt (SetSessionTelegramChatId, ClearSessionTelegramChatId, GetSessions, GetUserSessions). ConvertDataUrlToWebP und ResizeDataUrlToWebP zu einer Methode zusammengefasst. TelegramEndpoint: Bot-DTO-Mapping durch MapBotResponse() dedupliziert. KreaEndpoint + SkillEndpoint: Thumbnail-Generierung in ImageService.GenerateThumbnail() extrahiert, SixLabors-Imports aus Endpoints entfernt. CronjobEndpoint: 7x ctx.Items[UserId] durch ctx.GetUserId() ersetzt.
170 lines
6.1 KiB
C#
170 lines
6.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;
|
||
|
||
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;
|
||
}
|
||
|
||
var imagesDir = Path.GetDirectoryName(originalPath);
|
||
var dataDir = Path.GetDirectoryName(imagesDir);
|
||
var cacheDir = Path.Combine(dataDir, "cache", "images");
|
||
|
||
var thumbPath = await ImageService.GenerateThumbnail(originalPath, cacheDir);
|
||
if (thumbPath != null)
|
||
{
|
||
res.ContentType = "image/webp";
|
||
res.Headers["Cache-Control"] = "private, max-age=86400";
|
||
await res.SendFileAsync(thumbPath);
|
||
}
|
||
else
|
||
{
|
||
// Fallback: Original ausliefern
|
||
res.ContentType = ext switch
|
||
{
|
||
".png" => "image/png",
|
||
".jpg" => "image/jpeg",
|
||
".jpeg" => "image/jpeg",
|
||
".gif" => "image/gif",
|
||
_ => "image/webp",
|
||
};
|
||
res.Headers["Cache-Control"] = "private, max-age=3600";
|
||
await res.SendFileAsync(originalPath);
|
||
}
|
||
}
|
||
}
|
||
}
|