Files
gott/Endpoints/ChatEndpoint.cs
T
Meleki 0120bb8f73 Telegram: Bot=Session statt User-Level, Chat-Code-Refactoring
- Telegram-Config (Bot-Token, Webhook-Secret, ChatId) von User-Settings
  in sessions-Tabelle verschoben → jede Session hat ihren eigenen Bot
- Link-Code-System komplett entfernt (TelegramLinkRecord gelöscht)
- /start verknüpft automatisch, kein /link mehr nötig
- Alle Endpoints session-basiert: /api/sessions/{id}/telegram/*
- Webhook-URL pro Session: /api/telegram/webhook/{sessionId}

Refactoring: Doppelten Chat-Code eliminiert
- Neuer ChatService: PrepareContext, BuildHistoryFromDb, ResolveModelId
- Neues ChatContext-Model als gemeinsame Datenstruktur
- AnthropicService.ChatNonStreaming() für Telegram (statt eigenem Loop)
- TelegramService um ~180 Zeilen reduziert, kein eigener Claude-Loop mehr
- ChatEndpoint nutzt ChatService.PrepareContext() statt eigener Setup-Logik
- Model-Mapping nur noch an einer Stelle (3x → 1x)

Frontend
- Telegram-Section von Settings-Page in Session-Bearbeitungsseite verschoben
- Webhook-Secret readonly, wird einmalig generiert und persistent gespeichert
- Status wird automatisch geladen, Register/Delete je nach Webhook-Status
- Toast-Notification-System (showToast) in ui.js
- Link-Code-Modal und Telegram-Menüpunkt im Dashboard entfernt
- Telegram-Badge auf Session-Cards (konfiguriert/verbunden)

Bugfix: Bearer statt x-api-key für OAuth-Tokens im TelegramService
2026-03-03 21:46:59 +01:00

126 lines
4.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
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, IHttpClientFactory httpFactory, IConfiguration config, ChatService chatService, DatabaseService db, StreamingTracker streamingTracker, MemoryService memoryService, ToolService toolService, CancellationToken cancellationToken)
{
var body = await JsonSerializer.DeserializeAsync<ChatRequest>(req.Body);
if (body == null || body.Messages == null || body.Messages.Count == 0)
{
res.StatusCode = 400;
await res.WriteAsJsonAsync(new { error = "messages required" });
return;
}
var token = config["AnthropicToken"];
if (string.IsNullOrEmpty(token))
{
res.StatusCode = 500;
await res.WriteAsJsonAsync(new { error = "AnthropicToken not configured" });
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<int>("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<object>();
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<JsonElement>(node.ToJsonString());
}
messages.Add(new Dictionary<string, JsonElement>
{
["role"] = JsonSerializer.Deserialize<JsonElement>($"\"{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);
}
var anthropic = new AnthropicService(httpFactory, token, toolService, db, memoryService);
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);
}
}
}