Web-Suche (Agent-Toggle mit Versionswahl), Admin-only Backup, Haiku 4.5
- Agent: Web-Suche als Dropdown (Deaktiviert / 2025 / 2026) statt bool - AnthropicService: web_search Tool dynamisch nach Version einfügen - Stream: server_tool_use + web_search_tool_result Events verarbeiten - Chat-Frontend: web_search_start/end Events mit Suchindikator - Dashboard: Badge zeigt Web-Suche mit Jahresversion - UserRecord: IsAdmin Feld, DB-Migration is_admin Spalte - AuthMiddleware: IsAdmin aus DB laden und in Context setzen - BackupEndpoint: Admin-Check (403 für Nicht-Admins) - AuthEndpoint: isAdmin in Login/Me Response - Frontend: Backup-Section nur für Admins sichtbar - Haiku 4.5 als Model-Option (Agent-Dropdown + ChatEndpoint) - TODO: Cronjobs, Haiku ergänzt
This commit is contained in:
1 parent
485f47b29a
commit
8039b47dec
19 files changed
+155
-20
No files matched your search
@@ -62,7 +62,9 @@ public static class AgentEndpoint
|
|||||||
UseSoul = !body.TryGetProperty("useSoul", out var us) || us.GetBoolean(),
|
UseSoul = !body.TryGetProperty("useSoul", out var us) || us.GetBoolean(),
|
||||||
UseIdentity = !body.TryGetProperty("useIdentity", out var ui) || ui.GetBoolean(),
|
UseIdentity = !body.TryGetProperty("useIdentity", out var ui) || ui.GetBoolean(),
|
||||||
UseUserInfo = !body.TryGetProperty("useUserInfo", out var uu) || uu.GetBoolean(),
|
UseUserInfo = !body.TryGetProperty("useUserInfo", out var uu) || uu.GetBoolean(),
|
||||||
UseMemory = !body.TryGetProperty("useMemory", out var um) || um.GetBoolean()
|
UseMemory = !body.TryGetProperty("useMemory", out var um) || um.GetBoolean(),
|
||||||
|
UseWebSearch = body.TryGetProperty("webSearchVersion", out var wsv) && !string.IsNullOrEmpty(wsv.GetString()),
|
||||||
|
WebSearchVersion = body.TryGetProperty("webSearchVersion", out var wsv2) ? wsv2.GetString() ?? "" : ""
|
||||||
};
|
};
|
||||||
|
|
||||||
agent = await db.CreateAgent(agent);
|
agent = await db.CreateAgent(agent);
|
||||||
@@ -93,6 +95,11 @@ public static class AgentEndpoint
|
|||||||
if (body.TryGetProperty("useIdentity", out var ui)) agent.UseIdentity = ui.GetBoolean();
|
if (body.TryGetProperty("useIdentity", out var ui)) agent.UseIdentity = ui.GetBoolean();
|
||||||
if (body.TryGetProperty("useUserInfo", out var uu)) agent.UseUserInfo = uu.GetBoolean();
|
if (body.TryGetProperty("useUserInfo", out var uu)) agent.UseUserInfo = uu.GetBoolean();
|
||||||
if (body.TryGetProperty("useMemory", out var um)) agent.UseMemory = um.GetBoolean();
|
if (body.TryGetProperty("useMemory", out var um)) agent.UseMemory = um.GetBoolean();
|
||||||
|
if (body.TryGetProperty("webSearchVersion", out var wsv))
|
||||||
|
{
|
||||||
|
agent.WebSearchVersion = wsv.GetString() ?? "";
|
||||||
|
agent.UseWebSearch = !string.IsNullOrEmpty(agent.WebSearchVersion);
|
||||||
|
}
|
||||||
|
|
||||||
await db.UpdateAgent(agent);
|
await db.UpdateAgent(agent);
|
||||||
memoryService.InitializeAgentFiles(agentId, agent.Name);
|
memoryService.InitializeAgentFiles(agentId, agent.Name);
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ public static class AuthEndpoint
|
|||||||
{
|
{
|
||||||
userId = user.UserId,
|
userId = user.UserId,
|
||||||
username = user.Username,
|
username = user.Username,
|
||||||
displayName = user.DisplayName
|
displayName = user.DisplayName,
|
||||||
|
isAdmin = user.IsAdmin
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +92,8 @@ public static class AuthEndpoint
|
|||||||
{
|
{
|
||||||
userId = user.UserId,
|
userId = user.UserId,
|
||||||
username = user.Username,
|
username = user.Username,
|
||||||
displayName = user.DisplayName
|
displayName = user.DisplayName,
|
||||||
|
isAdmin = user.IsAdmin
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Gott.Extensions;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
@@ -15,8 +16,10 @@ public static class BackupEndpoint
|
|||||||
app.MapPost("/api/backup", HandleBackup);
|
app.MapPost("/api/backup", HandleBackup);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async System.Threading.Tasks.Task<IResult> HandleBackup()
|
private static async System.Threading.Tasks.Task<IResult> HandleBackup(HttpContext ctx)
|
||||||
{
|
{
|
||||||
|
if (!ctx.IsAdmin())
|
||||||
|
return Results.Forbid();
|
||||||
var dataDir = Path.Combine(AppContext.BaseDirectory, "data");
|
var dataDir = Path.Combine(AppContext.BaseDirectory, "data");
|
||||||
|
|
||||||
if (!Directory.Exists(dataDir))
|
if (!Directory.Exists(dataDir))
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ public static class ChatEndpoint
|
|||||||
{
|
{
|
||||||
"opus" => "claude-opus-4-6",
|
"opus" => "claude-opus-4-6",
|
||||||
"sonnet" => "claude-sonnet-4-6",
|
"sonnet" => "claude-sonnet-4-6",
|
||||||
|
"haiku" => "claude-haiku-4-5",
|
||||||
_ => "claude-sonnet-4-6"
|
_ => "claude-sonnet-4-6"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -185,7 +186,7 @@ public static class ChatEndpoint
|
|||||||
streamingTracker.Start(sessionId, chainId);
|
streamingTracker.Start(sessionId, chainId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await anthropic.StreamChat(res, modelId, systemPrompt, messages, chainId, workspace, sessionId, agentId: agent.AgentId, useMemory: agent.UseMemory, maxIterations: maxIterations, toolChoice: toolChoice, cancellationToken: cancellationToken);
|
await anthropic.StreamChat(res, modelId, systemPrompt, messages, chainId, workspace, sessionId, agentId: agent.AgentId, useMemory: agent.UseMemory, webSearchVersion: agent.WebSearchVersion, maxIterations: maxIterations, toolChoice: toolChoice, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,4 +8,9 @@ public static class HttpContextExtensions
|
|||||||
{
|
{
|
||||||
return ctx.Items["UserId"] as string;
|
return ctx.Items["UserId"] as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsAdmin(this HttpContext ctx)
|
||||||
|
{
|
||||||
|
return ctx.Items["IsAdmin"] is true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ public class AuthMiddleware
|
|||||||
_next = next;
|
_next = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext ctx, AuthService auth)
|
public async Task InvokeAsync(HttpContext ctx, AuthService auth, DatabaseService db)
|
||||||
{
|
{
|
||||||
var path = ctx.Request.Path.Value ?? "";
|
var path = ctx.Request.Path.Value ?? "";
|
||||||
|
|
||||||
@@ -35,6 +35,10 @@ public class AuthMiddleware
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.Items["UserId"] = userId;
|
ctx.Items["UserId"] = userId;
|
||||||
|
|
||||||
|
var user = await db.GetUser(userId);
|
||||||
|
ctx.Items["IsAdmin"] = user?.IsAdmin ?? false;
|
||||||
|
|
||||||
await _next(ctx);
|
await _next(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ public class AgentRecord
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Avatar { get; set; } // base64 data url
|
public string Avatar { get; set; } // base64 data url
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Model { get; set; } // sonnet, opus
|
public string Model { get; set; } // sonnet, opus, haiku
|
||||||
public string ToolChoice { get; set; } // "any" oder "auto"
|
public string ToolChoice { get; set; } // "any" oder "auto"
|
||||||
public string CustomCommands { get; set; }
|
public string CustomCommands { get; set; }
|
||||||
public string AutoCommit { get; set; } = ""; // "", "mention", "forbid"
|
public string AutoCommit { get; set; } = ""; // "", "mention", "forbid"
|
||||||
@@ -15,6 +15,8 @@ public class AgentRecord
|
|||||||
public bool UseIdentity { get; set; } = true;
|
public bool UseIdentity { get; set; } = true;
|
||||||
public bool UseUserInfo { get; set; } = true;
|
public bool UseUserInfo { get; set; } = true;
|
||||||
public bool UseMemory { get; set; } = true;
|
public bool UseMemory { get; set; } = true;
|
||||||
|
public bool UseWebSearch { get; set; }
|
||||||
|
public string WebSearchVersion { get; set; } = "";
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
public string CreatedAt { get; set; }
|
public string CreatedAt { get; set; }
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ public class UserRecord
|
|||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
public string CreatedAt { get; set; }
|
public string CreatedAt { get; set; }
|
||||||
public string UpdatedAt { get; set; }
|
public string UpdatedAt { get; set; }
|
||||||
|
public bool IsAdmin { get; set; }
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,7 @@ public class AnthropicService
|
|||||||
string sessionId = null,
|
string sessionId = null,
|
||||||
string agentId = null,
|
string agentId = null,
|
||||||
bool useMemory = true,
|
bool useMemory = true,
|
||||||
|
string webSearchVersion = null,
|
||||||
int maxIterations = 100,
|
int maxIterations = 100,
|
||||||
string toolChoice = "any",
|
string toolChoice = "any",
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
@@ -75,10 +76,17 @@ public class AnthropicService
|
|||||||
|
|
||||||
var hasWorkspace = !string.IsNullOrEmpty(workspace);
|
var hasWorkspace = !string.IsNullOrEmpty(workspace);
|
||||||
var includeMemory = useMemory && _memoryService != null && !string.IsNullOrEmpty(agentId);
|
var includeMemory = useMemory && _memoryService != null && !string.IsNullOrEmpty(agentId);
|
||||||
|
var hasTools = hasWorkspace || includeMemory;
|
||||||
|
var hasWebSearch = !string.IsNullOrEmpty(webSearchVersion);
|
||||||
|
|
||||||
if (hasWorkspace || includeMemory)
|
if (hasTools || hasWebSearch)
|
||||||
{
|
{
|
||||||
payload["tools"] = _toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory);
|
var tools = new List<object>();
|
||||||
|
if (hasTools)
|
||||||
|
tools.AddRange(_toolService.GetToolDefinitions(includeFileTools: hasWorkspace, includeMemory: includeMemory));
|
||||||
|
if (hasWebSearch)
|
||||||
|
tools.Add(new { type = webSearchVersion, name = "web_search", max_uses = 5 });
|
||||||
|
payload["tools"] = tools;
|
||||||
// "any" erzwingt Tool-Nutzung, aber nur in der ersten Iteration,
|
// "any" erzwingt Tool-Nutzung, aber nur in der ersten Iteration,
|
||||||
// damit Claude nicht endlos Tools aufruft wenn die Aufgabe erledigt ist.
|
// damit Claude nicht endlos Tools aufruft wenn die Aufgabe erledigt ist.
|
||||||
var effectiveChoice = (toolChoice == "any" && iterations == 1) ? "any" : "auto";
|
var effectiveChoice = (toolChoice == "any" && iterations == 1) ? "any" : "auto";
|
||||||
@@ -257,6 +265,7 @@ public class AnthropicService
|
|||||||
var currentTextBlock = new StringBuilder();
|
var currentTextBlock = new StringBuilder();
|
||||||
|
|
||||||
var blockTypes = new Dictionary<int, string>();
|
var blockTypes = new Dictionary<int, string>();
|
||||||
|
var webSearchResultBlocks = new Dictionary<int, object>();
|
||||||
var executedTools = new List<ToolResult>();
|
var executedTools = new List<ToolResult>();
|
||||||
|
|
||||||
string line;
|
string line;
|
||||||
@@ -313,6 +322,24 @@ public class AnthropicService
|
|||||||
currentToolInputJson.Clear();
|
currentToolInputJson.Clear();
|
||||||
await WriteSseEvent(response, "tool_start", new { name = currentToolName, id = currentToolId });
|
await WriteSseEvent(response, "tool_start", new { name = currentToolName, id = currentToolId });
|
||||||
}
|
}
|
||||||
|
else if (cbType == "server_tool_use")
|
||||||
|
{
|
||||||
|
if (currentTextBlock.Length > 0)
|
||||||
|
{
|
||||||
|
assistantContent.Add(new { type = "text", text = currentTextBlock.ToString() });
|
||||||
|
currentTextBlock.Clear();
|
||||||
|
}
|
||||||
|
currentToolId = cb.GetProperty("id").GetString() ?? "";
|
||||||
|
currentToolName = cb.GetProperty("name").GetString() ?? "";
|
||||||
|
currentToolInputJson.Clear();
|
||||||
|
await WriteSseEvent(response, "web_search_start", new { name = currentToolName, id = currentToolId });
|
||||||
|
}
|
||||||
|
else if (cbType == "web_search_tool_result")
|
||||||
|
{
|
||||||
|
// Gesamten Block als JSON speichern für die Conversation-History
|
||||||
|
var rawBlock = JsonSerializer.Deserialize<JsonElement>(cb.GetRawText());
|
||||||
|
webSearchResultBlocks[idx] = rawBlock;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,6 +406,27 @@ public class AnthropicService
|
|||||||
isError
|
isError
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else if (bt == "server_tool_use")
|
||||||
|
{
|
||||||
|
// Server-side tool (Web Search) – Anthropic führt es aus, wir speichern nur die Anfrage
|
||||||
|
JsonElement inputEl;
|
||||||
|
try { inputEl = JsonSerializer.Deserialize<JsonElement>(currentToolInputJson.ToString()); }
|
||||||
|
catch { inputEl = JsonSerializer.Deserialize<JsonElement>("{}"); }
|
||||||
|
|
||||||
|
assistantContent.Add(new
|
||||||
|
{
|
||||||
|
type = "server_tool_use",
|
||||||
|
id = currentToolId,
|
||||||
|
name = currentToolName,
|
||||||
|
input = inputEl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (bt == "web_search_tool_result")
|
||||||
|
{
|
||||||
|
// Web-Search-Ergebnis von Anthropic – als Raw-JSON in assistantContent aufnehmen
|
||||||
|
assistantContent.Add(webSearchResultBlocks[idx]);
|
||||||
|
await WriteSseEvent(response, "web_search_end", new { id = currentToolId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,9 +145,20 @@ public class DatabaseService
|
|||||||
TryAddColumn(conn, "agents", "use_user_info", "INTEGER NOT NULL DEFAULT 1");
|
TryAddColumn(conn, "agents", "use_user_info", "INTEGER NOT NULL DEFAULT 1");
|
||||||
TryAddColumn(conn, "agents", "use_memory", "INTEGER NOT NULL DEFAULT 1");
|
TryAddColumn(conn, "agents", "use_memory", "INTEGER NOT NULL DEFAULT 1");
|
||||||
|
|
||||||
|
TryAddColumn(conn, "agents", "use_web_search", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
TryAddColumn(conn, "agents", "web_search_version", "TEXT DEFAULT ''");
|
||||||
|
|
||||||
|
// Migration: use_web_search (bool) → web_search_version (text)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Execute("UPDATE agents SET web_search_version = 'web_search_20260209' WHERE use_web_search = 1 AND (web_search_version IS NULL OR web_search_version = '')");
|
||||||
|
}
|
||||||
|
catch { /* already migrated */ }
|
||||||
|
|
||||||
// User-Zuordnung
|
// User-Zuordnung
|
||||||
TryAddColumn(conn, "sessions", "user_id", "TEXT");
|
TryAddColumn(conn, "sessions", "user_id", "TEXT");
|
||||||
TryAddColumn(conn, "agents", "user_id", "TEXT");
|
TryAddColumn(conn, "agents", "user_id", "TEXT");
|
||||||
|
TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0");
|
||||||
|
|
||||||
conn.Execute(@"
|
conn.Execute(@"
|
||||||
CREATE TABLE IF NOT EXISTS agent_skills (
|
CREATE TABLE IF NOT EXISTS agent_skills (
|
||||||
@@ -189,8 +200,8 @@ public class DatabaseService
|
|||||||
agent.CreatedAt = now;
|
agent.CreatedAt = now;
|
||||||
agent.UpdatedAt = now;
|
agent.UpdatedAt = now;
|
||||||
await conn.ExecuteAsync(@"
|
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, is_deleted, created_at, updated_at)
|
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, is_deleted, created_at, updated_at)
|
||||||
VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, 0, @CreatedAt, @UpdatedAt)
|
VALUES (@AgentId, @UserId, @Name, @Avatar, @Description, @Model, @ToolChoice, @CustomCommands, @AutoCommit, @UseSoul, @UseIdentity, @UseUserInfo, @UseMemory, @UseWebSearch, @WebSearchVersion, 0, @CreatedAt, @UpdatedAt)
|
||||||
", agent);
|
", agent);
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
@@ -217,7 +228,7 @@ public class DatabaseService
|
|||||||
UPDATE agents SET name = @Name, avatar = @Avatar, description = @Description,
|
UPDATE agents SET name = @Name, avatar = @Avatar, description = @Description,
|
||||||
model = @Model, tool_choice = @ToolChoice,
|
model = @Model, tool_choice = @ToolChoice,
|
||||||
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_soul = @UseSoul, use_identity = @UseIdentity, use_user_info = @UseUserInfo, use_memory = @UseMemory, use_web_search = @UseWebSearch, web_search_version = @WebSearchVersion,
|
||||||
updated_at = @UpdatedAt
|
updated_at = @UpdatedAt
|
||||||
WHERE agent_id = @AgentId AND user_id = @UserId
|
WHERE agent_id = @AgentId AND user_id = @UserId
|
||||||
", agent);
|
", agent);
|
||||||
@@ -668,8 +679,8 @@ public class DatabaseService
|
|||||||
adminUserId = Guid.NewGuid().ToString("N");
|
adminUserId = Guid.NewGuid().ToString("N");
|
||||||
var now = DateTime.UtcNow.ToString("o");
|
var now = DateTime.UtcNow.ToString("o");
|
||||||
conn.Execute(@"
|
conn.Execute(@"
|
||||||
INSERT INTO users (user_id, username, password_hash, display_name, created_at, updated_at)
|
INSERT INTO users (user_id, username, password_hash, display_name, is_admin, created_at, updated_at)
|
||||||
VALUES (@userId, @username, @hash, @displayName, @now, @now)",
|
VALUES (@userId, @username, @hash, @displayName, 1, @now, @now)",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
userId = adminUserId,
|
userId = adminUserId,
|
||||||
@@ -687,6 +698,9 @@ public class DatabaseService
|
|||||||
// Bestehende Sessions/Agents ohne User dem ersten User zuweisen
|
// Bestehende Sessions/Agents ohne User dem ersten User zuweisen
|
||||||
conn.Execute("UPDATE sessions SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId });
|
conn.Execute("UPDATE sessions SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId });
|
||||||
conn.Execute("UPDATE agents SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId });
|
conn.Execute("UPDATE agents SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId });
|
||||||
|
|
||||||
|
// Erster User ist immer Admin
|
||||||
|
conn.Execute("UPDATE users SET is_admin = 1 WHERE user_id = @userId AND is_admin = 0", new { userId = adminUserId });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<UserRecord> GetUserByUsername(string username)
|
public async Task<UserRecord> GetUserByUsername(string username)
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
- [x] Anfrage/Stream abbrechen
|
- [x] Anfrage/Stream abbrechen
|
||||||
- [ ] Telegram-Intergration
|
- [ ] Telegram-Intergration
|
||||||
- [x] Refactoring: Agent/Session-Einstellungen, die den System-Prompt bzw. das Tool Use bearbeiten
|
- [x] Refactoring: Agent/Session-Einstellungen, die den System-Prompt bzw. das Tool Use bearbeiten
|
||||||
|
- [ ] Cronjobs
|
||||||
|
- [ ] Weiteres Modell hinzufügen: "Haiku 4.5"
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- [x] Wenn gerade gestreamt wird und man refreshed die Seite, dann verliert das Frontend den Status "Streaming..." und ich kann eine Nachricht schreiben, obwohl das nicht möglich sein sollte
|
- [x] Wenn gerade gestreamt wird und man refreshed die Seite, dann verliert das Frontend den Status "Streaming..." und ich kann eine Nachricht schreiben, obwohl das nicht möglich sein sollte
|
||||||
|
|||||||
+8
-1
@@ -134,6 +134,7 @@
|
|||||||
<select id="agentModel">
|
<select id="agentModel">
|
||||||
<option value="sonnet">Sonnet 4.6</option>
|
<option value="sonnet">Sonnet 4.6</option>
|
||||||
<option value="opus">Opus 4.6</option>
|
<option value="opus">Opus 4.6</option>
|
||||||
|
<option value="haiku">Haiku 4.5</option>
|
||||||
</select>
|
</select>
|
||||||
<label class="checkbox-label">
|
<label class="checkbox-label">
|
||||||
<input type="checkbox" id="agentToolChoice" checked>
|
<input type="checkbox" id="agentToolChoice" checked>
|
||||||
@@ -163,6 +164,12 @@
|
|||||||
<input type="checkbox" id="agentUseMemory" checked>
|
<input type="checkbox" id="agentUseMemory" checked>
|
||||||
Zugriff auf Erinnerungen <span class="checkbox-hint">(Memory-Tools verfügbar)</span>
|
Zugriff auf Erinnerungen <span class="checkbox-hint">(Memory-Tools verfügbar)</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label>Web-Suche</label>
|
||||||
|
<select id="agentWebSearchVersion">
|
||||||
|
<option value="">Deaktiviert</option>
|
||||||
|
<option value="web_search_20250305">Aktiviert (2025)</option>
|
||||||
|
<option value="web_search_20260209">Aktiviert (2026)</option>
|
||||||
|
</select>
|
||||||
<label>Skills</label>
|
<label>Skills</label>
|
||||||
<div id="agentSkillsSection" hidden>
|
<div id="agentSkillsSection" hidden>
|
||||||
<div class="skill-list" id="agentSkillList"></div>
|
<div class="skill-list" id="agentSkillList"></div>
|
||||||
@@ -261,7 +268,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="settings-section">
|
<section id="settingsBackupSection" class="settings-section" style="display:none">
|
||||||
<h2>Daten</h2>
|
<h2>Daten</h2>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<div class="setting-info">
|
<div class="setting-info">
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// ── Global User State ──
|
||||||
|
var currentUser = null;
|
||||||
|
|
||||||
// ── App Init ──
|
// ── App Init ──
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
@@ -8,6 +11,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentUser = user;
|
||||||
|
|
||||||
const { page, id } = parseHash();
|
const { page, id } = parseHash();
|
||||||
var target = page === 'login' ? 'dashboard' : page;
|
var target = page === 'login' ? 'dashboard' : page;
|
||||||
await navigateTo(target, { id }, { replace: true });
|
await navigateTo(target, { id }, { replace: true });
|
||||||
|
|||||||
@@ -184,6 +184,18 @@ async function sendMessage() {
|
|||||||
currentToolBox = null;
|
currentToolBox = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'web_search_start':
|
||||||
|
endStreamBubble();
|
||||||
|
currentToolBox = addToolBoxLoading('web_search');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'web_search_end':
|
||||||
|
if (currentToolBox) {
|
||||||
|
finishToolBox(currentToolBox, { name: 'web_search', result: 'Suche abgeschlossen', isError: false });
|
||||||
|
}
|
||||||
|
currentToolBox = null;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
endStreamBubble();
|
endStreamBubble();
|
||||||
addErrorBubble(data.error, true);
|
addErrorBubble(data.error, true);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ registerPage('agent', {
|
|||||||
const useIdentityEl = document.getElementById('agentUseIdentity');
|
const useIdentityEl = document.getElementById('agentUseIdentity');
|
||||||
const useUserInfoEl = document.getElementById('agentUseUserInfo');
|
const useUserInfoEl = document.getElementById('agentUseUserInfo');
|
||||||
const useMemoryEl = document.getElementById('agentUseMemory');
|
const useMemoryEl = document.getElementById('agentUseMemory');
|
||||||
|
const webSearchVersionEl = document.getElementById('agentWebSearchVersion');
|
||||||
const avatarPreview = document.getElementById('avatarPreview');
|
const avatarPreview = document.getElementById('avatarPreview');
|
||||||
const avatarInput = document.getElementById('avatarInput');
|
const avatarInput = document.getElementById('avatarInput');
|
||||||
const avatarBtn = document.getElementById('avatarBtn');
|
const avatarBtn = document.getElementById('avatarBtn');
|
||||||
@@ -49,6 +50,7 @@ registerPage('agent', {
|
|||||||
useIdentityEl.checked = agent.useIdentity !== false;
|
useIdentityEl.checked = agent.useIdentity !== false;
|
||||||
useUserInfoEl.checked = agent.useUserInfo !== false;
|
useUserInfoEl.checked = agent.useUserInfo !== false;
|
||||||
useMemoryEl.checked = agent.useMemory !== false;
|
useMemoryEl.checked = agent.useMemory !== false;
|
||||||
|
webSearchVersionEl.value = agent.webSearchVersion || '';
|
||||||
_agentAvatarData = agent.avatar || null;
|
_agentAvatarData = agent.avatar || null;
|
||||||
deleteBtn.hidden = false;
|
deleteBtn.hidden = false;
|
||||||
|
|
||||||
@@ -130,6 +132,7 @@ registerPage('agent', {
|
|||||||
useIdentity: useIdentityEl.checked,
|
useIdentity: useIdentityEl.checked,
|
||||||
useUserInfo: useUserInfoEl.checked,
|
useUserInfo: useUserInfoEl.checked,
|
||||||
useMemory: useMemoryEl.checked,
|
useMemory: useMemoryEl.checked,
|
||||||
|
webSearchVersion: webSearchVersionEl.value,
|
||||||
avatar: _agentAvatarData,
|
avatar: _agentAvatarData,
|
||||||
customCommands: getCommandsFromUI(commandsList)
|
customCommands: getCommandsFromUI(commandsList)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -152,6 +152,10 @@ registerPage('dashboard', {
|
|||||||
if (a.useIdentity) badges.push('<span class="badge badge-feature" title="Hat eine Identität (IDENTITY.md)"><i class="fa fa-id-badge"></i> Ich</span>');
|
if (a.useIdentity) badges.push('<span class="badge badge-feature" title="Hat eine Identität (IDENTITY.md)"><i class="fa fa-id-badge"></i> Ich</span>');
|
||||||
if (a.useUserInfo) badges.push('<span class="badge badge-feature" title="Kennt sein Gegenüber (USER.md)"><i class="fa fa-user"></i> Du</span>');
|
if (a.useUserInfo) badges.push('<span class="badge badge-feature" title="Kennt sein Gegenüber (USER.md)"><i class="fa fa-user"></i> Du</span>');
|
||||||
if (a.useMemory) badges.push('<span class="badge badge-feature" title="Auf Erinnerungen zugreifen (Memory-Tools)"><i class="fa fa-database"></i> Erinnerungen</span>');
|
if (a.useMemory) badges.push('<span class="badge badge-feature" title="Auf Erinnerungen zugreifen (Memory-Tools)"><i class="fa fa-database"></i> Erinnerungen</span>');
|
||||||
|
if (a.webSearchVersion) {
|
||||||
|
const wsYear = a.webSearchVersion.includes('2025') ? '2025' : '2026';
|
||||||
|
badges.push(`<span class="badge badge-feature" title="Web-Suche (${wsYear})"><i class="fa fa-search"></i> Web-Suche</span>`);
|
||||||
|
}
|
||||||
|
|
||||||
const skills = agentSkillsMap[a.agentId] || [];
|
const skills = agentSkillsMap[a.agentId] || [];
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ registerPage('login', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await apiLogin(username, password);
|
currentUser = await apiLogin(username, password);
|
||||||
navigateTo('dashboard');
|
navigateTo('dashboard');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorDiv.textContent = err.message || 'Login fehlgeschlagen';
|
errorDiv.textContent = err.message || 'Login fehlgeschlagen';
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ registerPage('settings', {
|
|||||||
const toolCallSelect = document.getElementById('settingToolCallDisplay');
|
const toolCallSelect = document.getElementById('settingToolCallDisplay');
|
||||||
const chainMetaSelect = document.getElementById('settingChainMetaDisplay');
|
const chainMetaSelect = document.getElementById('settingChainMetaDisplay');
|
||||||
const resetBtn = document.getElementById('settingsResetBtn');
|
const resetBtn = document.getElementById('settingsResetBtn');
|
||||||
|
const backupSection = document.getElementById('settingsBackupSection');
|
||||||
|
|
||||||
|
// Backup nur für Admins
|
||||||
|
if (currentUser && currentUser.isAdmin) {
|
||||||
|
backupSection.style.display = '';
|
||||||
|
}
|
||||||
|
|
||||||
const s = loadSettings();
|
const s = loadSettings();
|
||||||
slider.value = s.pageWidth;
|
slider.value = s.pageWidth;
|
||||||
|
|||||||
+13
-4
@@ -14,8 +14,9 @@ function prettyModelName(model) {
|
|||||||
if (minor && minor.length <= 2) return family + ' ' + major + '.' + minor;
|
if (minor && minor.length <= 2) return family + ' ' + major + '.' + minor;
|
||||||
return family + ' ' + major;
|
return family + ' ' + major;
|
||||||
}
|
}
|
||||||
if (model == 'sonnet') return 'Sonnet'
|
if (model == 'sonnet') return 'Sonnet 4.6'
|
||||||
if (model == 'opus') return 'Opus'
|
if (model == 'opus') return 'Opus 4.6'
|
||||||
|
if (model == 'haiku') return 'Haiku 4.5'
|
||||||
return 'Unbekannt (' + model + ')';
|
return 'Unbekannt (' + model + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +334,13 @@ function endStreamBubble() {
|
|||||||
// ── Tool Call Visibility ──
|
// ── Tool Call Visibility ──
|
||||||
|
|
||||||
const MODIFICATION_TOOLS = ['write_file', 'edit_file', 'delete_file', 'memory_write'];
|
const MODIFICATION_TOOLS = ['write_file', 'edit_file', 'delete_file', 'memory_write'];
|
||||||
|
const TOOL_DISPLAY_NAMES = {
|
||||||
|
write_file: 'Datei schreiben',
|
||||||
|
edit_file: 'Datei bearbeiten',
|
||||||
|
delete_file: 'Datei löschen',
|
||||||
|
memory_write: 'Erinnerungen festhalten',
|
||||||
|
web_search: 'Web-Suche'
|
||||||
|
};
|
||||||
|
|
||||||
function isToolVisible(toolName) {
|
function isToolVisible(toolName) {
|
||||||
const mode = loadSettings().toolCallDisplay;
|
const mode = loadSettings().toolCallDisplay;
|
||||||
@@ -362,7 +370,8 @@ function isChainMetaVisible(chainData) {
|
|||||||
function addToolBoxLoading(name) {
|
function addToolBoxLoading(name) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'tool-box streamed';
|
div.className = 'tool-box streamed';
|
||||||
div.innerHTML = '<span class="tool-name">' + esc(name) + '</span> <span class="tool-spinner"><i class="fa fa-hourglass-half"></i></span>';
|
const displayName = TOOL_DISPLAY_NAMES[name] || name;
|
||||||
|
div.innerHTML = '<span class="tool-name">' + esc(displayName) + '</span> <span class="tool-spinner"><i class="fa fa-hourglass-half"></i></span>';
|
||||||
chatEl().appendChild(div);
|
chatEl().appendChild(div);
|
||||||
scrollDown();
|
scrollDown();
|
||||||
return div;
|
return div;
|
||||||
@@ -377,7 +386,7 @@ function renderToolBoxContent(div, name, inputJson, result, isError) {
|
|||||||
|
|
||||||
const nameSpan = document.createElement('span');
|
const nameSpan = document.createElement('span');
|
||||||
nameSpan.className = 'tool-name';
|
nameSpan.className = 'tool-name';
|
||||||
nameSpan.textContent = name;
|
nameSpan.textContent = TOOL_DISPLAY_NAMES[name] || name;
|
||||||
header.appendChild(nameSpan);
|
header.appendChild(nameSpan);
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
|
|||||||
Reference in new issue
Block a user