using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Gott.Extensions; using Gott.Models; using Gott.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; namespace Gott.Endpoints; public static class ChatEndpoint { public static void MapChatEndpoint(this IEndpointRouteBuilder app) { app.MapPost("/api/chat", HandleChat); } private static async Task HandleChat(HttpRequest req, HttpResponse res, IConfiguration config, ChatService chatService, DatabaseService db, StreamingTracker streamingTracker, AnthropicService anthropic, CancellationToken cancellationToken) { var body = await JsonSerializer.DeserializeAsync(req.Body); if (body == null || body.Messages == null || body.Messages.Count == 0) { res.StatusCode = 400; await res.WriteAsJsonAsync(new { error = "messages required" }); return; } var sessionId = body.SessionId; if (string.IsNullOrEmpty(sessionId)) { res.StatusCode = 400; await res.WriteAsJsonAsync(new { error = "sessionId required" }); return; } var userId = req.HttpContext.GetUserId(); var maxIterations = config.GetValue("MaxIterations", 100); var ctx = await chatService.PrepareContext(sessionId, userId, maxIterations); if (ctx == null) { res.StatusCode = 400; await res.WriteAsJsonAsync(new { error = "Session/Agent nicht gefunden oder kein Agent ausgewählt" }); return; } res.Headers["Content-Type"] = "text/event-stream"; res.Headers["Cache-Control"] = "no-cache"; res.Headers["Connection"] = "keep-alive"; // Messages aus dem Request aufbereiten var messages = new List(); foreach (var msg in body.Messages) { JsonElement content = msg.Content; if (msg.Content.ValueKind == JsonValueKind.Array) { var node = System.Text.Json.Nodes.JsonNode.Parse(msg.Content.GetRawText()); foreach (var block in node.AsArray()) { if (block is System.Text.Json.Nodes.JsonObject obj) obj.Remove("_file_name"); } content = JsonSerializer.Deserialize(node.ToJsonString()); } messages.Add(new Dictionary { ["role"] = JsonSerializer.Deserialize($"\"{msg.Role}\""), ["content"] = content }); } // Nur die letzte User-Message speichern (= die neue) var lastUserMsg = body.Messages.LastOrDefault(m => m.Role == "user"); if (lastUserMsg != null) { var textContent = lastUserMsg.GetTextContent(); var attachmentBlocks = lastUserMsg.GetAttachmentBlocks(); var attachmentRecords = attachmentBlocks.Select(b => new AttachmentRecord { Type = b.Type, MediaType = b.MediaType, FileName = b.FileName, Data = b.Data }).ToList(); await db.SaveMessageWithAttachments(new MessageRecord { ChainId = ctx.ChainId, Iteration = 0, Role = "user", Type = "text", Content = textContent }, attachmentRecords); } streamingTracker.Start(sessionId, ctx.ChainId); try { await anthropic.StreamChat(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, ctx.Workspace, sessionId, agentId: ctx.AgentId, useMemory: ctx.UseMemory, webSearchVersion: ctx.WebSearchVersion, maxIterations: ctx.MaxIterations, toolChoice: ctx.ToolChoice, cancellationToken: cancellationToken); } finally { streamingTracker.Stop(sessionId); } } }