feat: Thumbnail-Varianten (256px) für Agent-Avatare und Session-Images
- ImageService: ResizeDataUrlSmall() für 256px WebP-Thumbnails (Quality 70) - Neue DB-Spalten: agents.avatar_small, sessions.image_small - Migration generiert Thumbnails für bestehende Bilder automatisch - Listen-Endpoints (/api/agents, /api/sessions) liefern kleine Variante - Detail-Endpoints liefern weiterhin das volle 512px-Bild - Frontend unverändert (Cards nutzen automatisch die kleineren Bilder)
This commit is contained in:
1 parent
8efce5f2d5
commit
2a2e98ca68
6 files changed
+116
-15
No files matched your search
@@ -8,6 +8,7 @@ using Gott.Models;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Webp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Gott.Services;
|
||||
|
||||
@@ -262,8 +263,14 @@ public class DatabaseService
|
||||
CREATE INDEX IF NOT EXISTS idx_shared_messages_message ON shared_messages(message_id);
|
||||
");
|
||||
|
||||
TryAddColumn(conn, "agents", "avatar_small", "TEXT");
|
||||
TryAddColumn(conn, "sessions", "image_small", "TEXT");
|
||||
|
||||
// Migration: Bestehende PNG/JPEG-Avatare/Images zu WebP konvertieren
|
||||
MigrateImagesToWebP(conn);
|
||||
|
||||
// Migration: Small-Thumbnails für bestehende Bilder generieren
|
||||
MigrateImageThumbnails(conn);
|
||||
}
|
||||
|
||||
private void MigrateImagesToWebP(SqliteConnection conn)
|
||||
@@ -298,6 +305,70 @@ public class DatabaseService
|
||||
Console.WriteLine($"[Migration] Converted {agentImages.Count} agent avatars and {sessionImages.Count} session images to WebP");
|
||||
}
|
||||
|
||||
private void MigrateImageThumbnails(SqliteConnection conn)
|
||||
{
|
||||
const int smallSize = 256;
|
||||
const int smallQuality = 70;
|
||||
|
||||
// Agent Avatare ohne Thumbnail
|
||||
var agentsWithoutThumb = conn.Query<(string AgentId, string Avatar)>(
|
||||
"SELECT agent_id, avatar FROM agents WHERE avatar IS NOT NULL AND avatar != '' AND (avatar_small IS NULL OR avatar_small = '')")
|
||||
.ToList();
|
||||
|
||||
foreach (var (agentId, avatar) in agentsWithoutThumb)
|
||||
{
|
||||
var small = ResizeDataUrlToWebP(avatar, smallSize, smallQuality);
|
||||
if (small != null)
|
||||
conn.Execute("UPDATE agents SET avatar_small = @small WHERE agent_id = @agentId", new { small, agentId });
|
||||
}
|
||||
|
||||
// Session Images ohne Thumbnail
|
||||
var sessionsWithoutThumb = conn.Query<(string SessionId, string Image)>(
|
||||
"SELECT session_id, image FROM sessions WHERE image IS NOT NULL AND image != '' AND (image_small IS NULL OR image_small = '')")
|
||||
.ToList();
|
||||
|
||||
foreach (var (sessionId, image) in sessionsWithoutThumb)
|
||||
{
|
||||
var small = ResizeDataUrlToWebP(image, smallSize, smallQuality);
|
||||
if (small != null)
|
||||
conn.Execute("UPDATE sessions SET image_small = @small WHERE session_id = @sessionId", new { small, sessionId });
|
||||
}
|
||||
|
||||
if (agentsWithoutThumb.Count > 0 || sessionsWithoutThumb.Count > 0)
|
||||
Console.WriteLine($"[Migration] Generated {agentsWithoutThumb.Count} agent thumbnails and {sessionsWithoutThumb.Count} session thumbnails");
|
||||
}
|
||||
|
||||
private static string ResizeDataUrlToWebP(string dataUrl, int maxSide, int quality)
|
||||
{
|
||||
try
|
||||
{
|
||||
var commaIndex = dataUrl.IndexOf(',');
|
||||
if (commaIndex < 0) return null;
|
||||
|
||||
var base64 = dataUrl.Substring(commaIndex + 1);
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
|
||||
using var image = Image.Load(bytes);
|
||||
if (image.Width > maxSide || image.Height > maxSide)
|
||||
{
|
||||
image.Mutate(x => x.Resize(new ResizeOptions
|
||||
{
|
||||
Size = new Size(maxSide, maxSide),
|
||||
Mode = ResizeMode.Max
|
||||
}));
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
image.Save(ms, new WebpEncoder { Quality = quality });
|
||||
|
||||
return "data:image/webp;base64," + Convert.ToBase64String(ms.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ConvertDataUrlToWebP(string dataUrl, int quality)
|
||||
{
|
||||
try
|
||||
@@ -395,8 +466,8 @@ public class DatabaseService
|
||||
agent.CreatedAt = now;
|
||||
agent.UpdatedAt = now;
|
||||
await conn.ExecuteAsync(@"
|
||||
INSERT INTO agents (agent_id, user_id, name, avatar, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_web_search, web_search_version, allow_user_choice, task_instruction, avatar_scale, avatar_offset_x, avatar_offset_y, is_deleted, created_at, updated_at)
|
||||
VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseWebSearch, @WebSearchVersion, @AllowUserChoice, @TaskInstruction, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, 0, @CreatedAt, @UpdatedAt)
|
||||
INSERT INTO agents (agent_id, user_id, name, avatar, avatar_small, description, model, tool_choice, custom_commands, auto_commit, use_soul, use_identity, use_user_info, use_memory, use_web_search, web_search_version, allow_user_choice, task_instruction, avatar_scale, avatar_offset_x, avatar_offset_y, is_deleted, created_at, updated_at)
|
||||
VALUES (@AgentId, @UserId, @Name, @Avatar, @AvatarSmall, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseWebSearch, @WebSearchVersion, @AllowUserChoice, @TaskInstruction, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, 0, @CreatedAt, @UpdatedAt)
|
||||
", agent);
|
||||
return agent;
|
||||
}
|
||||
@@ -420,7 +491,7 @@ public class DatabaseService
|
||||
using var conn = CreateConnection();
|
||||
agent.UpdatedAt = DateTime.UtcNow.ToString("o");
|
||||
await conn.ExecuteAsync(@"
|
||||
UPDATE agents SET name = @Name, avatar = @Avatar, description = @Description,
|
||||
UPDATE agents SET name = @Name, avatar = @Avatar, avatar_small = @AvatarSmall, description = @Description,
|
||||
model = @Model, tool_choice = @ToolChoice,
|
||||
custom_commands = @CustomCommands, auto_commit = @AutoCommit,
|
||||
use_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory, use_web_search = @UseWebSearch, web_search_version = @WebSearchVersion,
|
||||
@@ -440,7 +511,7 @@ public class DatabaseService
|
||||
|
||||
// ── Sessions ──
|
||||
|
||||
public async Task<SessionRecord> CreateSession(string userId, string name = null, string description = null, string image = null, string agentId = null, string workspace = null, string context = null, string customCommands = null, double avatarScale = 1.0, double avatarOffsetX = 0, double avatarOffsetY = 0, string reviewAgentId = null, int maxReviewIterations = 3, string finalizerAgentId = null, string preparerAgentId = null)
|
||||
public async Task<SessionRecord> CreateSession(string userId, string name = null, string description = null, string image = null, string imageSmall = null, string agentId = null, string workspace = null, string context = null, string customCommands = null, double avatarScale = 1.0, double avatarOffsetX = 0, double avatarOffsetY = 0, string reviewAgentId = null, int maxReviewIterations = 3, string finalizerAgentId = null, string preparerAgentId = null)
|
||||
{
|
||||
using var conn = CreateConnection();
|
||||
var now = DateTime.UtcNow.ToString("o");
|
||||
@@ -451,6 +522,7 @@ public class DatabaseService
|
||||
Name = name,
|
||||
Description = description,
|
||||
Image = image,
|
||||
ImageSmall = imageSmall,
|
||||
AgentId = agentId,
|
||||
Workspace = workspace,
|
||||
Context = context,
|
||||
@@ -466,8 +538,8 @@ public class DatabaseService
|
||||
UpdatedAt = now
|
||||
};
|
||||
await conn.ExecuteAsync(@"
|
||||
INSERT INTO sessions (session_id, user_id, name, description, image, agent_id, workspace, context, custom_commands, avatar_scale, avatar_offset_x, avatar_offset_y, review_agent_id, max_review_iterations, finalizer_agent_id, preparer_agent_id, created_at, updated_at)
|
||||
VALUES (@SessionId, @UserId, @Name, @Description, @Image, @AgentId, @Workspace, @Context, @CustomCommands, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, @ReviewAgentId, @MaxReviewIterations, @FinalizerAgentId, @PreparerAgentId, @CreatedAt, @UpdatedAt)
|
||||
INSERT INTO sessions (session_id, user_id, name, description, image, image_small, agent_id, workspace, context, custom_commands, avatar_scale, avatar_offset_x, avatar_offset_y, review_agent_id, max_review_iterations, finalizer_agent_id, preparer_agent_id, created_at, updated_at)
|
||||
VALUES (@SessionId, @UserId, @Name, @Description, @Image, @ImageSmall, @AgentId, @Workspace, @Context, @CustomCommands, @AvatarScale, @AvatarOffsetX, @AvatarOffsetY, @ReviewAgentId, @MaxReviewIterations, @FinalizerAgentId, @PreparerAgentId, @CreatedAt, @UpdatedAt)
|
||||
", session);
|
||||
return session;
|
||||
}
|
||||
@@ -541,7 +613,12 @@ public class DatabaseService
|
||||
if (body.TryGetProperty("image", out var img))
|
||||
{
|
||||
var imageData = img.GetString();
|
||||
if (imageService != null) imageData = imageService.ResizeDataUrl(imageData);
|
||||
if (imageService != null)
|
||||
{
|
||||
sets.Add("image_small = @imageSmall");
|
||||
p.Add("imageSmall", imageService.ResizeDataUrlSmall(imageData));
|
||||
imageData = imageService.ResizeDataUrl(imageData);
|
||||
}
|
||||
sets.Add("image = @image");
|
||||
p.Add("image", imageData);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user