diff --git a/Endpoints/AgentEndpoint.cs b/Endpoints/AgentEndpoint.cs index c290c8b..9201370 100644 --- a/Endpoints/AgentEndpoint.cs +++ b/Endpoints/AgentEndpoint.cs @@ -16,6 +16,7 @@ public static class AgentEndpoint public static void MapAgentEndpoints(this IEndpointRouteBuilder app) { app.MapGet("/api/agents", HandleList); + app.MapGet("/api/agents/available", HandleListAvailable); app.MapGet("/api/agents/{agentId}", HandleGet); app.MapPost("/api/agents", HandleCreate); app.MapPut("/api/agents/{agentId}", HandleUpdate); @@ -59,6 +60,7 @@ public static class AgentEndpoint a.AvatarScale, a.AvatarOffsetX, a.AvatarOffsetY, + a.IsPublic, a.IsDeleted, a.CreatedAt, a.UpdatedAt, @@ -69,6 +71,55 @@ public static class AgentEndpoint await ctx.Response.WriteAsJsonAsync(results); } + private static async Task HandleListAvailable(HttpContext ctx, DatabaseService db, SkillService skillService) + { + var userId = ctx.GetUserId(); + var agents = await db.GetAvailableAgents(userId); + var allSkills = skillService.GetAllSkills(); + var skillMap = allSkills.ToDictionary(s => s.Slug); + + var results = new List(); + foreach (var a in agents) + { + var agentSkillSlugs = await db.GetAgentSkills(a.AgentId); + var skills = agentSkillSlugs + .Where(slug => skillMap.ContainsKey(slug)) + .Select(slug => new { slug, name = skillMap[slug].Name, description = skillMap[slug].Description }) + .ToList(); + + results.Add(new + { + a.AgentId, + a.Name, + Avatar = a.AvatarSmall ?? a.Avatar, + a.Description, + a.Model, + a.ToolChoice, + a.CustomCommands, + a.AutoCommit, + a.UseSoul, + a.UseIdentity, + a.UseUserInfo, + a.UseMemory, + a.UseWebSearch, + a.WebSearchVersion, + a.AllowUserChoice, + a.TaskInstruction, + a.AvatarScale, + a.AvatarOffsetX, + a.AvatarOffsetY, + a.IsPublic, + a.IsDeleted, + a.CreatedAt, + a.UpdatedAt, + IsOwner = a.UserId == userId, + Skills = skills + }); + } + + await ctx.Response.WriteAsJsonAsync(results); + } + private static async Task HandleGet(HttpContext ctx, DatabaseService db) { var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); @@ -112,7 +163,8 @@ public static class AgentEndpoint TaskInstruction = body.TryGetProperty("taskInstruction", out var ti) ? ti.GetString() : null, AvatarScale = body.TryGetProperty("avatarScale", out var asc) ? asc.GetDouble() : 1.0, AvatarOffsetX = body.TryGetProperty("avatarOffsetX", out var aox) ? aox.GetDouble() : 0, - AvatarOffsetY = body.TryGetProperty("avatarOffsetY", out var aoy) ? aoy.GetDouble() : 0 + AvatarOffsetY = body.TryGetProperty("avatarOffsetY", out var aoy) ? aoy.GetDouble() : 0, + IsPublic = body.TryGetProperty("isPublic", out var ip) && ip.GetBoolean() }; agent = await db.CreateAgent(agent); @@ -157,6 +209,7 @@ public static class AgentEndpoint if (body.TryGetProperty("avatarScale", out var asc)) agent.AvatarScale = asc.GetDouble(); if (body.TryGetProperty("avatarOffsetX", out var aox)) agent.AvatarOffsetX = aox.GetDouble(); if (body.TryGetProperty("avatarOffsetY", out var aoy)) agent.AvatarOffsetY = aoy.GetDouble(); + if (body.TryGetProperty("isPublic", out var ip)) agent.IsPublic = ip.GetBoolean(); await db.UpdateAgent(agent); memoryService.InitializeAgentFiles(agentId, agent.Name); diff --git a/Endpoints/TelegramEndpoint.cs b/Endpoints/TelegramEndpoint.cs index 34af9ab..007910b 100644 --- a/Endpoints/TelegramEndpoint.cs +++ b/Endpoints/TelegramEndpoint.cs @@ -250,15 +250,16 @@ public static class TelegramEndpoint foreach (var bot in bots) { var isOwner = bot.UserId == userId; + var sessionCount = await db.GetTelegramBotSessionCount(bot.BotId); result.Add(new { botId = bot.BotId, name = bot.Name, isOwner, isPublic = bot.IsPublic, + sessionCount, createdAt = bot.CreatedAt, - // Token nur für eigene Bots anzeigen - botToken = isOwner ? bot.BotToken : null + updatedAt = bot.UpdatedAt }); } await res.WriteAsJsonAsync(result); diff --git a/Models/AgentRecord.cs b/Models/AgentRecord.cs index f40dbcb..2d312b7 100644 --- a/Models/AgentRecord.cs +++ b/Models/AgentRecord.cs @@ -24,6 +24,7 @@ public class AgentRecord public double AvatarOffsetX { get; set; } public double AvatarOffsetY { get; set; } public string UserId { get; set; } + public bool IsPublic { get; set; } public bool IsDeleted { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 7127c65..645e0ed 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -284,6 +284,7 @@ public class DatabaseService "); TryAddColumn(conn, "agents", "avatar_small", "TEXT"); + TryAddColumn(conn, "agents", "is_public", "INTEGER NOT NULL DEFAULT 0"); TryAddColumn(conn, "sessions", "image_small", "TEXT"); // Migration: Bestehende PNG/JPEG-Avatare/Images zu WebP konvertieren @@ -499,6 +500,13 @@ public class DatabaseService "SELECT * FROM agents WHERE user_id = @userId AND is_deleted = 0 ORDER BY name", new { userId }); } + public async Task> GetAvailableAgents(string userId) + { + using var conn = CreateConnection(); + return await conn.QueryAsync( + "SELECT * FROM agents WHERE (user_id = @userId OR is_public = 1) AND is_deleted = 0 ORDER BY name", new { userId }); + } + public async Task GetAgent(string agentId) { using var conn = CreateConnection(); @@ -516,7 +524,8 @@ public class DatabaseService 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, allow_user_choice = @AllowUserChoice, task_instruction = @TaskInstruction, - avatar_scale = @AvatarScale, avatar_offset_x = @AvatarOffsetX, avatar_offset_y = @AvatarOffsetY, updated_at = @UpdatedAt + avatar_scale = @AvatarScale, avatar_offset_x = @AvatarOffsetX, avatar_offset_y = @AvatarOffsetY, + is_public = @IsPublic, updated_at = @UpdatedAt WHERE agent_id = @AgentId AND user_id = @UserId ", agent); } diff --git a/wwwroot/index.html b/wwwroot/index.html index 92b583c..111fada 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -247,6 +247,10 @@ +
diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index 7075204..eafccb0 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -68,6 +68,12 @@ async function apiGetAgents() { return resp.json(); } +async function apiGetAvailableAgents() { + const resp = await fetch('/api/agents/available'); + if (!resp.ok) return []; + return resp.json(); +} + async function apiGetAgent(agentId) { const resp = await fetch(`/api/agents/${agentId}`); if (!resp.ok) return null; diff --git a/wwwroot/js/cards.js b/wwwroot/js/cards.js index 1109153..b49aa2c 100644 --- a/wwwroot/js/cards.js +++ b/wwwroot/js/cards.js @@ -113,6 +113,9 @@ function buildAgentCard(agent, skills) { badges.push(` Web-Suche`); } + if (agent.isPublic) badges.push(' Public'); + if (agent.isOwner === false) badges.push(' Fremd'); + if (skills) { for (const skill of skills) { badges.push(` ${esc(skill.name)}`); diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js index f90c9b4..d07b69f 100644 --- a/wwwroot/js/pages/agent-page.js +++ b/wwwroot/js/pages/agent-page.js @@ -34,6 +34,7 @@ registerPage('agent', { const addCommandBtn = document.getElementById('agentAddCommandBtn'); const skillsSection = document.getElementById('agentSkillsSection'); const skillList = document.getElementById('agentSkillList'); + const isPublicEl = document.getElementById('agentIsPublic'); const more = document.querySelector('.more'); // Avatar Editor initialisieren @@ -66,6 +67,7 @@ registerPage('agent', { useUserInfoEl.checked = agent.useUserInfo !== false; useMemoryEl.checked = agent.useMemory !== false; webSearchVersionEl.value = agent.webSearchVersion || ''; + isPublicEl.checked = agent.isPublic || false; deleteBtn.hidden = false; // Custom Commands laden @@ -112,7 +114,8 @@ registerPage('agent', { avatarOffsetX: _agentAvatarEditor.getOffsetX(), avatarOffsetY: _agentAvatarEditor.getOffsetY(), customCommands: getCommandsFromUI(commandsList), - taskInstruction: _agentTaskMde ? _agentTaskMde.value().trim() || null : null + taskInstruction: _agentTaskMde ? _agentTaskMde.value().trim() || null : null, + isPublic: isPublicEl.checked }; if (isNew) { diff --git a/wwwroot/js/pages/cronjob-page.js b/wwwroot/js/pages/cronjob-page.js index bbfbcc2..2667c52 100644 --- a/wwwroot/js/pages/cronjob-page.js +++ b/wwwroot/js/pages/cronjob-page.js @@ -20,7 +20,7 @@ registerPage('cronjob', { // Sessions + Agents laden const sessions = await apiGetSessions(); - const agents = await apiGetAgents(); + const agents = await apiGetAvailableAgents(); const agentMap = {}; for (const a of agents) agentMap[a.agentId] = a; const sessionMap = {}; diff --git a/wwwroot/js/pages/dashboard.js b/wwwroot/js/pages/dashboard.js index 7dd5a80..0247796 100644 --- a/wwwroot/js/pages/dashboard.js +++ b/wwwroot/js/pages/dashboard.js @@ -35,11 +35,11 @@ registerPage('dashboard', { // Telegram-Config parallel pro Session laden (sessions werden gleich geladen) - const [agents, sessions, cronjobs, shares, telegramBots] = await Promise.all([ - apiGetAgents(), apiGetSessions(), apiGetCronjobs(), apiListShares(), apiGetTelegramBots() + const [agents, availableAgents, sessions, cronjobs, shares, telegramBots] = await Promise.all([ + apiGetAgents(), apiGetAvailableAgents(), apiGetSessions(), apiGetCronjobs(), apiListShares(), apiGetTelegramBots() ]); const agentMap = {}; - for (const a of agents) agentMap[a.agentId] = a; + for (const a of availableAgents) agentMap[a.agentId] = a; // Sessions const grid = document.getElementById('sessionCards'); diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js index f4a1a6c..aa75394 100644 --- a/wwwroot/js/pages/session-page.js +++ b/wwwroot/js/pages/session-page.js @@ -75,7 +75,7 @@ registerPage('session', { } // Agents laden + Skills parallel - const agents = await apiGetAgents(); + const agents = await apiGetAvailableAgents(); const agentMap = {}; for (const a of agents) agentMap[a.agentId] = a;