feat: Public-Flag für Agents + verfügbare Agents/Bots Endpoints erweitert
- AgentRecord: neues IsPublic-Feld mit DB-Migration - GET /api/agents/available: eigene + public Agents (alle Properties + isOwner) - GET /api/telegram/bots/available: alle Properties außer Token/Secret + sessionCount - Agent-Page: Public-Checkbox (andere User können Agent in Sessions verwenden) - Dashboard: agentMap aus availableAgents gebaut (public Agents fremder User auflösbar) - Session-Page + Cronjob-Page: Agent-Picker nutzt /available statt /agents - Agent-Cards: Public- und Fremd-Badges - Fix: Chat-Button bei Sessions mit public Agent eines anderen Users nicht mehr disabled
This commit is contained in:
1 parent
ec73ccd71e
commit
b0d8bef5ea
11 files changed
+90
-10
No files matched your search
@@ -16,6 +16,7 @@ public static class AgentEndpoint
|
|||||||
public static void MapAgentEndpoints(this IEndpointRouteBuilder app)
|
public static void MapAgentEndpoints(this IEndpointRouteBuilder app)
|
||||||
{
|
{
|
||||||
app.MapGet("/api/agents", HandleList);
|
app.MapGet("/api/agents", HandleList);
|
||||||
|
app.MapGet("/api/agents/available", HandleListAvailable);
|
||||||
app.MapGet("/api/agents/{agentId}", HandleGet);
|
app.MapGet("/api/agents/{agentId}", HandleGet);
|
||||||
app.MapPost("/api/agents", HandleCreate);
|
app.MapPost("/api/agents", HandleCreate);
|
||||||
app.MapPut("/api/agents/{agentId}", HandleUpdate);
|
app.MapPut("/api/agents/{agentId}", HandleUpdate);
|
||||||
@@ -59,6 +60,7 @@ public static class AgentEndpoint
|
|||||||
a.AvatarScale,
|
a.AvatarScale,
|
||||||
a.AvatarOffsetX,
|
a.AvatarOffsetX,
|
||||||
a.AvatarOffsetY,
|
a.AvatarOffsetY,
|
||||||
|
a.IsPublic,
|
||||||
a.IsDeleted,
|
a.IsDeleted,
|
||||||
a.CreatedAt,
|
a.CreatedAt,
|
||||||
a.UpdatedAt,
|
a.UpdatedAt,
|
||||||
@@ -69,6 +71,55 @@ public static class AgentEndpoint
|
|||||||
await ctx.Response.WriteAsJsonAsync(results);
|
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<object>();
|
||||||
|
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)
|
private static async Task HandleGet(HttpContext ctx, DatabaseService db)
|
||||||
{
|
{
|
||||||
var agentId = ctx.Request.RouteValues["agentId"]?.ToString();
|
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,
|
TaskInstruction = body.TryGetProperty("taskInstruction", out var ti) ? ti.GetString() : null,
|
||||||
AvatarScale = body.TryGetProperty("avatarScale", out var asc) ? asc.GetDouble() : 1.0,
|
AvatarScale = body.TryGetProperty("avatarScale", out var asc) ? asc.GetDouble() : 1.0,
|
||||||
AvatarOffsetX = body.TryGetProperty("avatarOffsetX", out var aox) ? aox.GetDouble() : 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);
|
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("avatarScale", out var asc)) agent.AvatarScale = asc.GetDouble();
|
||||||
if (body.TryGetProperty("avatarOffsetX", out var aox)) agent.AvatarOffsetX = aox.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("avatarOffsetY", out var aoy)) agent.AvatarOffsetY = aoy.GetDouble();
|
||||||
|
if (body.TryGetProperty("isPublic", out var ip)) agent.IsPublic = ip.GetBoolean();
|
||||||
|
|
||||||
await db.UpdateAgent(agent);
|
await db.UpdateAgent(agent);
|
||||||
memoryService.InitializeAgentFiles(agentId, agent.Name);
|
memoryService.InitializeAgentFiles(agentId, agent.Name);
|
||||||
|
|||||||
@@ -250,15 +250,16 @@ public static class TelegramEndpoint
|
|||||||
foreach (var bot in bots)
|
foreach (var bot in bots)
|
||||||
{
|
{
|
||||||
var isOwner = bot.UserId == userId;
|
var isOwner = bot.UserId == userId;
|
||||||
|
var sessionCount = await db.GetTelegramBotSessionCount(bot.BotId);
|
||||||
result.Add(new
|
result.Add(new
|
||||||
{
|
{
|
||||||
botId = bot.BotId,
|
botId = bot.BotId,
|
||||||
name = bot.Name,
|
name = bot.Name,
|
||||||
isOwner,
|
isOwner,
|
||||||
isPublic = bot.IsPublic,
|
isPublic = bot.IsPublic,
|
||||||
|
sessionCount,
|
||||||
createdAt = bot.CreatedAt,
|
createdAt = bot.CreatedAt,
|
||||||
// Token nur für eigene Bots anzeigen
|
updatedAt = bot.UpdatedAt
|
||||||
botToken = isOwner ? bot.BotToken : null
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await res.WriteAsJsonAsync(result);
|
await res.WriteAsJsonAsync(result);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class AgentRecord
|
|||||||
public double AvatarOffsetX { get; set; }
|
public double AvatarOffsetX { get; set; }
|
||||||
public double AvatarOffsetY { get; set; }
|
public double AvatarOffsetY { get; set; }
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
|
public bool IsPublic { get; set; }
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
public string CreatedAt { get; set; }
|
public string CreatedAt { get; set; }
|
||||||
public string UpdatedAt { get; set; }
|
public string UpdatedAt { get; set; }
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ public class DatabaseService
|
|||||||
");
|
");
|
||||||
|
|
||||||
TryAddColumn(conn, "agents", "avatar_small", "TEXT");
|
TryAddColumn(conn, "agents", "avatar_small", "TEXT");
|
||||||
|
TryAddColumn(conn, "agents", "is_public", "INTEGER NOT NULL DEFAULT 0");
|
||||||
TryAddColumn(conn, "sessions", "image_small", "TEXT");
|
TryAddColumn(conn, "sessions", "image_small", "TEXT");
|
||||||
|
|
||||||
// Migration: Bestehende PNG/JPEG-Avatare/Images zu WebP konvertieren
|
// 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 });
|
"SELECT * FROM agents WHERE user_id = @userId AND is_deleted = 0 ORDER BY name", new { userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<AgentRecord>> GetAvailableAgents(string userId)
|
||||||
|
{
|
||||||
|
using var conn = CreateConnection();
|
||||||
|
return await conn.QueryAsync<AgentRecord>(
|
||||||
|
"SELECT * FROM agents WHERE (user_id = @userId OR is_public = 1) AND is_deleted = 0 ORDER BY name", new { userId });
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<AgentRecord> GetAgent(string agentId)
|
public async Task<AgentRecord> GetAgent(string agentId)
|
||||||
{
|
{
|
||||||
using var conn = CreateConnection();
|
using var conn = CreateConnection();
|
||||||
@@ -516,7 +524,8 @@ public class DatabaseService
|
|||||||
custom_commands = @CustomCommands, auto_commit = @AutoCommit,
|
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,
|
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,
|
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
|
WHERE agent_id = @AgentId AND user_id = @UserId
|
||||||
", agent);
|
", agent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,6 +247,10 @@
|
|||||||
<button type="button" id="agentAddCommandBtn" class="add-cmd-btn">+ Befehl hinzufügen</button>
|
<button type="button" id="agentAddCommandBtn" class="add-cmd-btn">+ Befehl hinzufügen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="agentIsPublic">
|
||||||
|
Public <span class="checkbox-hint">(andere User können diesen Agent in ihren Sessions verwenden)</span>
|
||||||
|
</label>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button id="agentSaveBtn">Speichern</button>
|
<button id="agentSaveBtn">Speichern</button>
|
||||||
<button id="agentCancelBtn">Abbrechen</button>
|
<button id="agentCancelBtn">Abbrechen</button>
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ async function apiGetAgents() {
|
|||||||
return resp.json();
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function apiGetAvailableAgents() {
|
||||||
|
const resp = await fetch('/api/agents/available');
|
||||||
|
if (!resp.ok) return [];
|
||||||
|
return resp.json();
|
||||||
|
}
|
||||||
|
|
||||||
async function apiGetAgent(agentId) {
|
async function apiGetAgent(agentId) {
|
||||||
const resp = await fetch(`/api/agents/${agentId}`);
|
const resp = await fetch(`/api/agents/${agentId}`);
|
||||||
if (!resp.ok) return null;
|
if (!resp.ok) return null;
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ function buildAgentCard(agent, skills) {
|
|||||||
badges.push(`<span class="badge badge-feature" title="Web-Suche - ${wsYear}"><i class="fa fa-search"></i> Web-Suche</span>`);
|
badges.push(`<span class="badge badge-feature" title="Web-Suche - ${wsYear}"><i class="fa fa-search"></i> Web-Suche</span>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (agent.isPublic) badges.push('<span class="badge badge-feature" title="Public – für alle User verfügbar"><i class="fa fa-globe"></i> Public</span>');
|
||||||
|
if (agent.isOwner === false) badges.push('<span class="badge badge-other" title="Gehört einem anderen User"><i class="fa fa-user-o"></i> Fremd</span>');
|
||||||
|
|
||||||
if (skills) {
|
if (skills) {
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
badges.push(`<span class="badge badge-skill" title="${esc(skill.description || skill.slug)}"><i class="fa fa-puzzle-piece"></i> ${esc(skill.name)}</span>`);
|
badges.push(`<span class="badge badge-skill" title="${esc(skill.description || skill.slug)}"><i class="fa fa-puzzle-piece"></i> ${esc(skill.name)}</span>`);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ registerPage('agent', {
|
|||||||
const addCommandBtn = document.getElementById('agentAddCommandBtn');
|
const addCommandBtn = document.getElementById('agentAddCommandBtn');
|
||||||
const skillsSection = document.getElementById('agentSkillsSection');
|
const skillsSection = document.getElementById('agentSkillsSection');
|
||||||
const skillList = document.getElementById('agentSkillList');
|
const skillList = document.getElementById('agentSkillList');
|
||||||
|
const isPublicEl = document.getElementById('agentIsPublic');
|
||||||
const more = document.querySelector('.more');
|
const more = document.querySelector('.more');
|
||||||
|
|
||||||
// Avatar Editor initialisieren
|
// Avatar Editor initialisieren
|
||||||
@@ -66,6 +67,7 @@ registerPage('agent', {
|
|||||||
useUserInfoEl.checked = agent.useUserInfo !== false;
|
useUserInfoEl.checked = agent.useUserInfo !== false;
|
||||||
useMemoryEl.checked = agent.useMemory !== false;
|
useMemoryEl.checked = agent.useMemory !== false;
|
||||||
webSearchVersionEl.value = agent.webSearchVersion || '';
|
webSearchVersionEl.value = agent.webSearchVersion || '';
|
||||||
|
isPublicEl.checked = agent.isPublic || false;
|
||||||
deleteBtn.hidden = false;
|
deleteBtn.hidden = false;
|
||||||
|
|
||||||
// Custom Commands laden
|
// Custom Commands laden
|
||||||
@@ -112,7 +114,8 @@ registerPage('agent', {
|
|||||||
avatarOffsetX: _agentAvatarEditor.getOffsetX(),
|
avatarOffsetX: _agentAvatarEditor.getOffsetX(),
|
||||||
avatarOffsetY: _agentAvatarEditor.getOffsetY(),
|
avatarOffsetY: _agentAvatarEditor.getOffsetY(),
|
||||||
customCommands: getCommandsFromUI(commandsList),
|
customCommands: getCommandsFromUI(commandsList),
|
||||||
taskInstruction: _agentTaskMde ? _agentTaskMde.value().trim() || null : null
|
taskInstruction: _agentTaskMde ? _agentTaskMde.value().trim() || null : null,
|
||||||
|
isPublic: isPublicEl.checked
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ registerPage('cronjob', {
|
|||||||
|
|
||||||
// Sessions + Agents laden
|
// Sessions + Agents laden
|
||||||
const sessions = await apiGetSessions();
|
const sessions = await apiGetSessions();
|
||||||
const agents = await apiGetAgents();
|
const agents = await apiGetAvailableAgents();
|
||||||
const agentMap = {};
|
const agentMap = {};
|
||||||
for (const a of agents) agentMap[a.agentId] = a;
|
for (const a of agents) agentMap[a.agentId] = a;
|
||||||
const sessionMap = {};
|
const sessionMap = {};
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ registerPage('dashboard', {
|
|||||||
|
|
||||||
// Telegram-Config parallel pro Session laden (sessions werden gleich geladen)
|
// Telegram-Config parallel pro Session laden (sessions werden gleich geladen)
|
||||||
|
|
||||||
const [agents, sessions, cronjobs, shares, telegramBots] = await Promise.all([
|
const [agents, availableAgents, sessions, cronjobs, shares, telegramBots] = await Promise.all([
|
||||||
apiGetAgents(), apiGetSessions(), apiGetCronjobs(), apiListShares(), apiGetTelegramBots()
|
apiGetAgents(), apiGetAvailableAgents(), apiGetSessions(), apiGetCronjobs(), apiListShares(), apiGetTelegramBots()
|
||||||
]);
|
]);
|
||||||
const agentMap = {};
|
const agentMap = {};
|
||||||
for (const a of agents) agentMap[a.agentId] = a;
|
for (const a of availableAgents) agentMap[a.agentId] = a;
|
||||||
|
|
||||||
// Sessions
|
// Sessions
|
||||||
const grid = document.getElementById('sessionCards');
|
const grid = document.getElementById('sessionCards');
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ registerPage('session', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Agents laden + Skills parallel
|
// Agents laden + Skills parallel
|
||||||
const agents = await apiGetAgents();
|
const agents = await apiGetAvailableAgents();
|
||||||
const agentMap = {};
|
const agentMap = {};
|
||||||
for (const a of agents) agentMap[a.agentId] = a;
|
for (const a of agents) agentMap[a.agentId] = a;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user