diff --git a/Endpoints/AgentEndpoint.cs b/Endpoints/AgentEndpoint.cs new file mode 100644 index 0000000..024b073 --- /dev/null +++ b/Endpoints/AgentEndpoint.cs @@ -0,0 +1,87 @@ +using System.Text.Json; +using System.Threading.Tasks; +using Gott.Models; +using Gott.Services; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace Gott.Endpoints; + +public static class AgentEndpoint +{ + public static void MapAgentEndpoints(this IEndpointRouteBuilder app) + { + app.MapGet("/api/agents", HandleList); + app.MapGet("/api/agents/{agentId}", HandleGet); + app.MapPost("/api/agents", HandleCreate); + app.MapPut("/api/agents/{agentId}", HandleUpdate); + app.MapDelete("/api/agents/{agentId}", HandleDelete); + } + + private static async Task HandleList(HttpContext ctx, DatabaseService db) + { + var agents = await db.GetAgents(); + await ctx.Response.WriteAsJsonAsync(agents); + } + + private static async Task HandleGet(HttpContext ctx, DatabaseService db) + { + var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); + if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; } + + var agent = await db.GetAgent(agentId); + if (agent == null) { ctx.Response.StatusCode = 404; return; } + + await ctx.Response.WriteAsJsonAsync(agent); + } + + private static async Task HandleCreate(HttpContext ctx, DatabaseService db) + { + var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); + + var agent = new AgentRecord + { + Name = body.TryGetProperty("name", out var n) ? n.GetString() ?? "Neuer Agent" : "Neuer Agent", + Avatar = body.TryGetProperty("avatar", out var av) ? av.GetString() : null, + Description = body.TryGetProperty("description", out var d) ? d.GetString() : null, + Model = body.TryGetProperty("model", out var m) ? m.GetString() ?? "sonnet" : "sonnet", + Persona = body.TryGetProperty("persona", out var p) ? p.GetString() : null, + Instructions = body.TryGetProperty("instructions", out var i) ? i.GetString() : null + }; + + agent = await db.CreateAgent(agent); + await ctx.Response.WriteAsJsonAsync(agent); + } + + private static async Task HandleUpdate(HttpContext ctx, DatabaseService db) + { + var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); + if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; } + + var agent = await db.GetAgent(agentId); + if (agent == null) { ctx.Response.StatusCode = 404; return; } + + var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); + + if (body.TryGetProperty("name", out var n)) agent.Name = n.GetString(); + if (body.TryGetProperty("avatar", out var av)) agent.Avatar = av.GetString(); + if (body.TryGetProperty("description", out var d)) agent.Description = d.GetString(); + if (body.TryGetProperty("model", out var m)) agent.Model = m.GetString(); + if (body.TryGetProperty("persona", out var p)) agent.Persona = p.GetString(); + if (body.TryGetProperty("instructions", out var i)) agent.Instructions = i.GetString(); + + await db.UpdateAgent(agent); + agent = await db.GetAgent(agentId); + await ctx.Response.WriteAsJsonAsync(agent); + } + + private static async Task HandleDelete(HttpContext ctx, DatabaseService db) + { + var agentId = ctx.Request.RouteValues["agentId"]?.ToString(); + if (string.IsNullOrEmpty(agentId)) { ctx.Response.StatusCode = 400; return; } + + await db.DeleteAgent(agentId); + ctx.Response.StatusCode = 204; + } +} diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs index 0df4d8f..9afced9 100644 --- a/Endpoints/ChatEndpoint.cs +++ b/Endpoints/ChatEndpoint.cs @@ -39,7 +39,32 @@ public static class ChatEndpoint return; } - var modelId = body.Model switch + var sessionId = body.SessionId; + if (string.IsNullOrEmpty(sessionId)) + { + res.StatusCode = 400; + await res.WriteAsJsonAsync(new { error = "sessionId required" }); + return; + } + + // Agent aus Session auflösen + var session = await db.GetSession(sessionId); + if (session == null || string.IsNullOrEmpty(session.AgentId)) + { + res.StatusCode = 400; + await res.WriteAsJsonAsync(new { error = "Kein Agent ausgewählt" }); + return; + } + + var agent = await db.GetAgent(session.AgentId); + if (agent == null) + { + res.StatusCode = 400; + await res.WriteAsJsonAsync(new { error = "Agent nicht gefunden" }); + return; + } + + var modelId = agent.Model switch { "opus" => "claude-opus-4-6", "sonnet" => "claude-sonnet-4-6", @@ -50,20 +75,13 @@ public static class ChatEndpoint res.Headers["Cache-Control"] = "no-cache"; res.Headers["Connection"] = "keep-alive"; - var sessionId = body.SessionId; - if (string.IsNullOrEmpty(sessionId)) - { - res.StatusCode = 400; - await res.WriteAsJsonAsync(new { error = "sessionId required" }); - return; - } - var chainId = Guid.NewGuid().ToString("N"); await db.CreateChain(new ChainRecord { ChainId = chainId, SessionId = sessionId, + AgentId = agent.AgentId, Model = modelId, SystemPrompt = body.SystemPrompt }); @@ -89,13 +107,24 @@ public static class ChatEndpoint }); } - var systemPrompt = new List - { - new { type = "text", text = "You are Claude Code, Anthropic's official CLI for Claude." } - }; + var systemPrompt = new List(); + + // Agent Persona + if (!string.IsNullOrWhiteSpace(agent.Persona)) + systemPrompt.Add(new { type = "text", text = agent.Persona }); + + // Agent Instructions + if (!string.IsNullOrWhiteSpace(agent.Instructions)) + systemPrompt.Add(new { type = "text", text = agent.Instructions }); + + // Zusätzlicher System Prompt aus dem UI if (!string.IsNullOrWhiteSpace(body.SystemPrompt)) systemPrompt.Add(new { type = "text", text = body.SystemPrompt }); + // Fallback wenn gar nix gesetzt + if (systemPrompt.Count == 0) + systemPrompt.Add(new { type = "text", text = "Du bist ein hilfreicher Assistent." }); + var anthropic = new AnthropicService(httpFactory, token, toolService, db); await anthropic.StreamChat(res, modelId, systemPrompt, messages, chainId); } diff --git a/Endpoints/SessionEndpoint.cs b/Endpoints/SessionEndpoint.cs index 25b8b69..7fe7257 100644 --- a/Endpoints/SessionEndpoint.cs +++ b/Endpoints/SessionEndpoint.cs @@ -61,6 +61,10 @@ public static class SessionEndpoint { await db.UpdateSessionName(sessionId, nameEl.GetString()); } + if (body.TryGetProperty("agentId", out var agentEl)) + { + await db.UpdateSessionAgent(sessionId, agentEl.GetString()); + } var session = await db.GetSession(sessionId); await ctx.Response.WriteAsJsonAsync(session); diff --git a/Models/AgentRecord.cs b/Models/AgentRecord.cs new file mode 100644 index 0000000..fae3445 --- /dev/null +++ b/Models/AgentRecord.cs @@ -0,0 +1,16 @@ +namespace Gott.Models; + +public class AgentRecord +{ + public long Id { get; set; } + public string AgentId { get; set; } + public string Name { get; set; } + public string Avatar { get; set; } // base64 data url + public string Description { get; set; } + public string Model { get; set; } // sonnet, opus + public string Persona { get; set; } + public string Instructions { get; set; } + public bool IsDeleted { get; set; } + public string CreatedAt { get; set; } + public string UpdatedAt { get; set; } +} diff --git a/Models/ChainRecord.cs b/Models/ChainRecord.cs index d6ba6de..2a85ba2 100644 --- a/Models/ChainRecord.cs +++ b/Models/ChainRecord.cs @@ -5,6 +5,7 @@ public class ChainRecord public long Id { get; set; } public string ChainId { get; set; } public string SessionId { get; set; } + public string AgentId { get; set; } public string Model { get; set; } public string SystemPrompt { get; set; } public long InputTokens { get; set; } diff --git a/Models/ChatModels.cs b/Models/ChatModels.cs index 545147e..b67ebb3 100644 --- a/Models/ChatModels.cs +++ b/Models/ChatModels.cs @@ -14,9 +14,6 @@ public class ChatMessage public class ChatRequest { - [JsonPropertyName("model")] - public string Model { get; set; } = "sonnet"; - [JsonPropertyName("messages")] public List Messages { get; set; } = new(); diff --git a/Models/SessionRecord.cs b/Models/SessionRecord.cs index cdf2e68..7e1dbdc 100644 --- a/Models/SessionRecord.cs +++ b/Models/SessionRecord.cs @@ -5,6 +5,7 @@ public class SessionRecord public long Id { get; set; } public string SessionId { get; set; } public string Name { get; set; } + public string AgentId { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } } diff --git a/Program.cs b/Program.cs index 2167ef2..1abe648 100644 --- a/Program.cs +++ b/Program.cs @@ -22,5 +22,6 @@ app.UseStaticFiles(); app.MapChatEndpoint(); app.MapHistoryEndpoint(); app.MapSessionEndpoints(); +app.MapAgentEndpoints(); app.Run("http://0.0.0.0:19000"); diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 9abf314..cd8042c 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -78,10 +78,83 @@ public class DatabaseService FOREIGN KEY (chain_id) REFERENCES chains(chain_id) ); + CREATE TABLE IF NOT EXISTS agents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + agent_id TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + avatar TEXT, + description TEXT, + model TEXT NOT NULL DEFAULT 'sonnet', + persona TEXT, + instructions TEXT, + is_deleted INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + CREATE INDEX IF NOT EXISTS idx_chains_session ON chains(session_id); CREATE INDEX IF NOT EXISTS idx_chains_archive ON chains(archive_id); CREATE INDEX IF NOT EXISTS idx_messages_chain ON messages(chain_id); "); + + // Migrations + TryAddColumn(conn, "sessions", "agent_id", "TEXT"); + TryAddColumn(conn, "chains", "agent_id", "TEXT"); + } + + private void TryAddColumn(SqliteConnection conn, string table, string column, string type) + { + try { conn.Execute($"ALTER TABLE {table} ADD COLUMN {column} {type}"); } + catch { /* column already exists */ } + } + + // ── Agents ── + + public async Task CreateAgent(AgentRecord agent) + { + using var conn = CreateConnection(); + var now = DateTime.UtcNow.ToString("o"); + agent.AgentId = Guid.NewGuid().ToString("N"); + agent.CreatedAt = now; + agent.UpdatedAt = now; + await conn.ExecuteAsync(@" + INSERT INTO agents (agent_id, name, avatar, description, model, persona, instructions, is_deleted, created_at, updated_at) + VALUES (@AgentId, @Name, @Avatar, @Description, @Model, @Persona, @Instructions, 0, @CreatedAt, @UpdatedAt) + ", agent); + return agent; + } + + public async Task> GetAgents() + { + using var conn = CreateConnection(); + return await conn.QueryAsync( + "SELECT * FROM agents WHERE is_deleted = 0 ORDER BY name"); + } + + public async Task GetAgent(string agentId) + { + using var conn = CreateConnection(); + return await conn.QuerySingleOrDefaultAsync( + "SELECT * FROM agents WHERE agent_id = @agentId AND is_deleted = 0", new { agentId }); + } + + public async Task UpdateAgent(AgentRecord agent) + { + using var conn = CreateConnection(); + agent.UpdatedAt = DateTime.UtcNow.ToString("o"); + await conn.ExecuteAsync(@" + UPDATE agents SET name = @Name, avatar = @Avatar, description = @Description, + model = @Model, persona = @Persona, instructions = @Instructions, updated_at = @UpdatedAt + WHERE agent_id = @AgentId + ", agent); + } + + public async Task DeleteAgent(string agentId) + { + using var conn = CreateConnection(); + await conn.ExecuteAsync( + "UPDATE agents SET is_deleted = 1, updated_at = @now WHERE agent_id = @agentId", + new { agentId, now = DateTime.UtcNow.ToString("o") }); } // ── Sessions ── @@ -134,6 +207,14 @@ public class DatabaseService new { sessionId, name, now = DateTime.UtcNow.ToString("o") }); } + public async Task UpdateSessionAgent(string sessionId, string agentId) + { + using var conn = CreateConnection(); + await conn.ExecuteAsync( + "UPDATE sessions SET agent_id = @agentId, updated_at = @now WHERE session_id = @sessionId", + new { sessionId, agentId, now = DateTime.UtcNow.ToString("o") }); + } + // ── Archives ── public async Task CreateArchive(string sessionId, string name = null) @@ -195,8 +276,8 @@ public class DatabaseService using var conn = CreateConnection(); chain.CreatedAt = DateTime.UtcNow.ToString("o"); await conn.ExecuteAsync(@" - INSERT INTO chains (chain_id, session_id, archive_id, model, system_prompt, input_tokens, output_tokens, iterations, stop_reason, duration_ms, created_at) - VALUES (@ChainId, @SessionId, @ArchiveId, @Model, @SystemPrompt, @InputTokens, @OutputTokens, @Iterations, @StopReason, @DurationMs, @CreatedAt) + INSERT INTO chains (chain_id, session_id, archive_id, agent_id, model, system_prompt, input_tokens, output_tokens, iterations, stop_reason, duration_ms, created_at) + VALUES (@ChainId, @SessionId, @ArchiveId, @AgentId, @Model, @SystemPrompt, @InputTokens, @OutputTokens, @Iterations, @StopReason, @DurationMs, @CreatedAt) ", chain); } diff --git a/wwwroot/css/main.css b/wwwroot/css/main.css index 1669968..c77bb8e 100644 --- a/wwwroot/css/main.css +++ b/wwwroot/css/main.css @@ -129,3 +129,83 @@ body { .placeholder-icon { font-size: 4em; } + +/* ── Agent Form ── */ + +.agent-form { + padding: calc(var(--s4) * 2); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: var(--s3); + max-width: 600px; +} + +.agent-form label { + font-weight: bold; +} + +.agent-form textarea { + resize: vertical; +} + +.agent-avatar-section { + display: flex; + align-items: center; + gap: var(--s4); +} + +.agent-avatar-preview { + width: 64px; + height: 64px; + background: var(--f2-bg); + border: var(--border-width) solid var(--f2-border); + display: flex; + align-items: center; + justify-content: center; + font-size: 2em; + overflow: hidden; + flex-shrink: 0; +} + +.agent-avatar-preview img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.agent-form-actions { + display: flex; + gap: var(--s4); + margin-top: var(--s4); +} + +.danger-btn { + color: #ff6b6b; + border-color: #ff6b6b; +} + +.danger-btn:hover { + background: #ff6b6b; + color: var(--f1-bg); +} + +/* ── Agent Card Avatar ── */ + +.agent-card-avatar { + width: 40px; + height: 40px; + background: var(--f2-bg); + border: var(--border-width) solid var(--f2-border); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2em; + overflow: hidden; +} + +.agent-card-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} diff --git a/wwwroot/index.html b/wwwroot/index.html index af693e3..d2b6a46 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -52,10 +52,7 @@

Session

- +
@@ -76,12 +73,33 @@
-

Agent

+

Neuer Agent

-
-
🤖
-

Agents

-

Hier wird der ausgewählte Agent angezeigt.

+
+
+
?
+ + + +
+ + + + + + + + + + +
+ + + +
@@ -107,6 +125,7 @@ + diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index 6804558..d4162a5 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -1,13 +1,48 @@ // ── API Calls ── -async function apiChat(model, messages, systemPrompt, sessionId) { +async function apiChat(messages, systemPrompt, sessionId) { return fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ model, messages, systemPrompt, sessionId }) + body: JSON.stringify({ messages, systemPrompt, sessionId }) }); } +// ── Agent API ── + +async function apiGetAgents() { + const resp = await fetch('/api/agents'); + return resp.json(); +} + +async function apiGetAgent(agentId) { + const resp = await fetch(`/api/agents/${agentId}`); + if (!resp.ok) return null; + return resp.json(); +} + +async function apiCreateAgent(data) { + const resp = await fetch('/api/agents', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + return resp.json(); +} + +async function apiUpdateAgent(agentId, data) { + const resp = await fetch(`/api/agents/${agentId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + return resp.json(); +} + +async function apiDeleteAgent(agentId) { + return fetch(`/api/agents/${agentId}`, { method: 'DELETE' }); +} + async function apiHistory(sessionId) { const resp = await fetch(`/api/history?sessionId=${sessionId}`); return resp.json(); diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js index 8c48f3a..cfa9b4d 100644 --- a/wwwroot/js/chat.js +++ b/wwwroot/js/chat.js @@ -21,7 +21,6 @@ async function clearChat() { async function sendMessage() { const input = $('#input'); const btn = $('#send'); - const model = $('#model').value; const systemPrompt = $('#systemPrompt').value.trim(); const text = input.value.trim(); if (!text) return; @@ -39,7 +38,7 @@ async function sendMessage() { let currentToolBox = null; try { - const resp = await apiChat(model, messages, systemPrompt, getCurrentSessionId()); + const resp = await apiChat(messages, systemPrompt, getCurrentSessionId()); const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; @@ -92,7 +91,7 @@ async function sendMessage() { case 'done': endStreamBubble(); - let meta = `Model: ${data.model || model} | In: ${data.inputTokens || 0} | Out: ${data.outputTokens || 0}`; + let meta = `Model: ${data.model || '?'} | In: ${data.inputTokens || 0} | Out: ${data.outputTokens || 0}`; if (data.iterations > 1) meta += ` | Iterations: ${data.iterations}`; if (data.durationMs) meta += ` | ${(data.durationMs / 1000).toFixed(1)}s`; setMeta(meta); diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js new file mode 100644 index 0000000..10eed25 --- /dev/null +++ b/wwwroot/js/pages/agent-page.js @@ -0,0 +1,98 @@ +// ── Agent Page ── + +let _agentAvatarData = null; + +registerPage('agent', { + async init(params) { + const agentId = params.id; + const isNew = !agentId; + let agent = null; + + if (!isNew) { + agent = await apiGetAgent(agentId); + if (!agent) return navigateTo('dashboard'); + } + + const title = document.getElementById('agentPageTitle'); + const nameEl = document.getElementById('agentName'); + const descEl = document.getElementById('agentDescription'); + const modelEl = document.getElementById('agentModel'); + const personaEl = document.getElementById('agentPersona'); + const instructionsEl = document.getElementById('agentInstructions'); + const avatarPreview = document.getElementById('avatarPreview'); + const avatarInput = document.getElementById('avatarInput'); + const avatarBtn = document.getElementById('avatarBtn'); + const avatarClearBtn = document.getElementById('avatarClearBtn'); + const saveBtn = document.getElementById('agentSaveBtn'); + const cancelBtn = document.getElementById('agentCancelBtn'); + const deleteBtn = document.getElementById('agentDeleteBtn'); + + _agentAvatarData = null; + + if (agent) { + title.textContent = agent.name; + nameEl.value = agent.name || ''; + descEl.value = agent.description || ''; + modelEl.value = agent.model || 'sonnet'; + personaEl.value = agent.persona || ''; + instructionsEl.value = agent.instructions || ''; + _agentAvatarData = agent.avatar || null; + deleteBtn.hidden = false; + + if (agent.avatar) { + avatarPreview.innerHTML = `Avatar`; + } + } + + // Avatar Upload + avatarBtn.addEventListener('click', () => avatarInput.click()); + avatarInput.addEventListener('change', () => { + const file = avatarInput.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + _agentAvatarData = reader.result; + avatarPreview.innerHTML = `Avatar`; + }; + reader.readAsDataURL(file); + }); + + avatarClearBtn.addEventListener('click', () => { + _agentAvatarData = null; + avatarPreview.innerHTML = '?'; + avatarInput.value = ''; + }); + + // Speichern + saveBtn.addEventListener('click', async () => { + const data = { + name: nameEl.value.trim() || 'Unbenannt', + description: descEl.value.trim(), + model: modelEl.value, + persona: personaEl.value, + instructions: instructionsEl.value, + avatar: _agentAvatarData + }; + + if (isNew) { + await apiCreateAgent(data); + } else { + await apiUpdateAgent(agentId, data); + } + navigateTo('dashboard'); + }); + + // Abbrechen + cancelBtn.addEventListener('click', () => navigateTo('dashboard')); + + // Löschen + deleteBtn.addEventListener('click', async () => { + if (!confirm('Agent wirklich löschen?')) return; + await apiDeleteAgent(agentId); + navigateTo('dashboard'); + }); + }, + destroy() { + _agentAvatarData = null; + } +}); diff --git a/wwwroot/js/pages/dashboard.js b/wwwroot/js/pages/dashboard.js index a1ec0e1..b33caec 100644 --- a/wwwroot/js/pages/dashboard.js +++ b/wwwroot/js/pages/dashboard.js @@ -2,6 +2,7 @@ registerPage('dashboard', { async init() { + // Sessions laden const sessions = await apiGetSessions(); const grid = document.getElementById('sessionCards'); grid.innerHTML = ''; @@ -19,14 +20,42 @@ registerPage('dashboard', { grid.appendChild(card); } - // Neue Session Button - const btn = document.getElementById('newSessionBtn'); - if (btn) { - btn.addEventListener('click', async () => { + const newSessionBtn = document.getElementById('newSessionBtn'); + if (newSessionBtn) { + newSessionBtn.addEventListener('click', async () => { const session = await apiCreateSession(); navigateTo('session', { id: session.sessionId }); }); } + + // Agents laden + const agents = await apiGetAgents(); + const agentGrid = document.getElementById('agentCards'); + agentGrid.innerHTML = ''; + + for (const a of agents) { + const card = document.createElement('div'); + card.className = 'card clickable'; + card.innerHTML = ` +
${a.avatar ? `` : '?'}
+
${escHtml(a.name)}
+
${escHtml(a.model || 'sonnet')}
+ `; + card.addEventListener('click', () => { + navigateTo('agent', { id: a.agentId }); + }); + agentGrid.appendChild(card); + } + + // Neuer Agent Placeholder + const placeholder = document.createElement('div'); + placeholder.className = 'card placeholder clickable'; + placeholder.innerHTML = ` +
+
+
Neuer Agent
+ `; + placeholder.addEventListener('click', () => navigateTo('agent')); + agentGrid.appendChild(placeholder); }, destroy() {} }); diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js index b7f3663..092fbc1 100644 --- a/wwwroot/js/pages/session-page.js +++ b/wwwroot/js/pages/session-page.js @@ -1,7 +1,5 @@ // ── Session Page ── -let _sessionPageCleanup = []; - registerPage('session', { async init(params) { const sessionId = params.id; @@ -9,7 +7,7 @@ registerPage('session', { currentSessionId = sessionId; - // Session-Name laden + // Session laden const session = await apiGetSession(sessionId); const titleEl = document.getElementById('sessionTitle'); if (session && titleEl) { @@ -17,6 +15,42 @@ registerPage('session', { titleEl.addEventListener('dblclick', () => editTitle(sessionId, titleEl)); } + // Agent-Auswahl laden + const agentSelect = document.getElementById('agentSelect'); + const sendBtn = document.getElementById('send'); + const inputEl = document.getElementById('input'); + + if (agentSelect) { + const agents = await apiGetAgents(); + agentSelect.innerHTML = ''; + for (const a of agents) { + const opt = document.createElement('option'); + opt.value = a.agentId; + opt.textContent = `${a.name} (${a.model})`; + agentSelect.appendChild(opt); + } + + // Gespeicherten Agent setzen + if (session && session.agentId) { + agentSelect.value = session.agentId; + } + + // Agent-Wechsel speichern + agentSelect.addEventListener('change', async () => { + const val = agentSelect.value || null; + await apiUpdateSession(sessionId, { agentId: val }); + updateSendState(); + }); + } + + // Send-Button State + function updateSendState() { + const hasAgent = agentSelect && agentSelect.value; + if (sendBtn) sendBtn.disabled = !hasAgent; + if (inputEl) inputEl.disabled = !hasAgent; + } + updateSendState(); + // System Prompt Toggle const showSystem = document.getElementById('showSystem'); const systemPrompt = document.getElementById('systemPrompt'); @@ -27,18 +61,13 @@ registerPage('session', { } // Send - const sendBtn = document.getElementById('send'); if (sendBtn) sendBtn.addEventListener('click', sendMessage); // Ctrl+Enter - const inputEl = document.getElementById('input'); if (inputEl) { - const keyHandler = (e) => { + inputEl.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'Enter') sendMessage(); - }; - inputEl.addEventListener('keydown', keyHandler); - - // Auto-Resize + }); inputEl.addEventListener('input', function () { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 150) + 'px';