Refactor: Server-Settings von appsettings.json in DB-Tabelle (server_settings)
- Neuer ServerSettingsService: zentrale Settings in SQLite mit In-Memory-Cache - Auto-Migration: bestehende appsettings.Production.json wird einmalig in DB übernommen - Alle Services umgestellt: IConfiguration → ServerSettingsService (dynamisch, kein Neustart) - Port bleibt als einziges Setting in appsettings.Production.json (erfordert Neustart) - ServerSettingsEndpoint: liest/schreibt DB statt JSON-Datei, Port als Sonder-Setting - IConfiguration komplett entfernt aus allen Services (ChatService, ToolService, AnthropicService, OpenAiService, KreaService, TelegramService, KeyScannerService, GitHubEventScannerService) - AuthMiddleware: KreaApiKey dynamisch aus DB - appsettings.json bereinigt: nur noch Port und Logging - Dokumentation aktualisiert (ARCHITECTURE.md, ARCHITECTURE_QUICK.md, README.md)
This commit is contained in:
1 parent
542a031172
commit
122000487c
18 files changed
+389
-270
No files matched your search
+1
-10
@@ -1812,16 +1812,7 @@ Die Anwendung ist als systemd-Service konfiguriert und läuft standardmäßig au
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Port": 19000, // Server-Port (Default 19000, erfordert Neustart)
|
"Port": 19000 // Server-Port (Default 19000, erfordert Neustart)
|
||||||
"MaxIterations": 100, // Max Tool-Loop Iterationen pro Chat
|
|
||||||
"MaxHistory": 120, // Max Messages in Chat-History
|
|
||||||
"ReminderInterval": 30, // System-Prompt-Reminder alle N Messages
|
|
||||||
"ExecuteTimeout": 180, // Timeout für Shell-Befehle in Sekunden
|
|
||||||
"PayloadLogging": false, // API-Payloads loggen (Debug)
|
|
||||||
"PayloadLoggingGzip": false, // Payload-Logs als .gz komprimieren
|
|
||||||
"KreaToken": "", // Krea API Token (optional)
|
|
||||||
"KreaApiKey": "", // API-Key für Krea-Endpoints ohne User-Auth (optional)
|
|
||||||
"GeminiApiKey": "" // Google Gemini API Key (kostenlos via aistudio.google.com/apikey)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+1
-10
@@ -275,16 +275,7 @@ User-Input → POST /api/chat (SSE)
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Port": 19000, // Server-Port (erfordert Neustart)
|
"Port": 19000 // Server-Port (Default 19000, erfordert Neustart)
|
||||||
"MaxIterations": 100, // Max Tool-Loop Iterationen pro Chat
|
|
||||||
"MaxHistory": 120, // Max Messages in Chat-History
|
|
||||||
"ReminderInterval": 30, // System-Prompt-Reminder alle N Messages
|
|
||||||
"ExecuteTimeout": 180, // Timeout für Shell-Befehle in Sekunden
|
|
||||||
"PayloadLogging": false, // API-Payloads loggen (Debug)
|
|
||||||
"PayloadLoggingGzip": false, // Payload-Logs als .gz komprimieren
|
|
||||||
"KreaToken": "", // Krea API Token (optional)
|
|
||||||
"KreaApiKey": "", // API-Key für Krea-Endpoints ohne User-Auth (optional)
|
|
||||||
"GeminiApiKey": "" // Google Gemini API Key (optional, kostenlos)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using Gott.Services;
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace Gott.Endpoints;
|
namespace Gott.Endpoints;
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ public static class ChatEndpoint
|
|||||||
app.MapPost("/api/chat/stop", HandleStop);
|
app.MapPost("/api/chat/stop", HandleStop);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleChat(HttpRequest req, HttpResponse res, IConfiguration config, ChatService chatService, DatabaseService db, StreamingTracker streamingTracker, AnthropicService anthropic, OpenAiService openAi, RemoteConnectionManager remoteMgr, EmbeddingBackgroundService embeddingBg, CancellationToken cancellationToken)
|
private static async Task HandleChat(HttpRequest req, HttpResponse res, ServerSettingsService settings, ChatService chatService, DatabaseService db, StreamingTracker streamingTracker, AnthropicService anthropic, OpenAiService openAi, RemoteConnectionManager remoteMgr, EmbeddingBackgroundService embeddingBg, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var body = await JsonSerializer.DeserializeAsync<ChatRequest>(req.Body);
|
var body = await JsonSerializer.DeserializeAsync<ChatRequest>(req.Body);
|
||||||
if (body == null)
|
if (body == null)
|
||||||
@@ -61,7 +60,7 @@ public static class ChatEndpoint
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxIterations = config.GetValue<int>("MaxIterations", 100);
|
var maxIterations = settings.GetInt("MaxIterations", 100);
|
||||||
|
|
||||||
var hasRemote = remoteMgr.GetBySessionId(sessionId) != null;
|
var hasRemote = remoteMgr.GetBySessionId(sessionId) != null;
|
||||||
var ctx = await chatService.PrepareContext(sessionId, userId, maxIterations, planMode: body.PlanMode, hasRemoteConnection: hasRemote);
|
var ctx = await chatService.PrepareContext(sessionId, userId, maxIterations, planMode: body.PlanMode, hasRemoteConnection: hasRemote);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Text.Json;
|
|||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Gott.Extensions;
|
using Gott.Extensions;
|
||||||
|
using Gott.Services;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@@ -13,42 +14,13 @@ namespace Gott.Endpoints;
|
|||||||
|
|
||||||
public static class ServerSettingsEndpoint
|
public static class ServerSettingsEndpoint
|
||||||
{
|
{
|
||||||
// Pfade werden beim MapEndpoints gesetzt – basierend auf ContentRootPath,
|
|
||||||
// da .NET die Config-Dateien vom ContentRoot lädt (= WorkingDirectory),
|
|
||||||
// nicht von AppContext.BaseDirectory (= Executable-Verzeichnis).
|
|
||||||
private static string ProductionConfigPath;
|
|
||||||
private static string FallbackConfigPath;
|
|
||||||
|
|
||||||
// Definiert alle konfigurierbaren Properties mit Typ und Standardwert
|
|
||||||
private static readonly List<SettingDefinition> Definitions = new List<SettingDefinition>
|
|
||||||
{
|
|
||||||
new SettingDefinition("Port", "int", 19000, "Server-Port (erfordert Neustart)", "number"),
|
|
||||||
new SettingDefinition("MaxIterations", "int", 100, "Max Tool-Loop Iterationen pro Chat", "number"),
|
|
||||||
new SettingDefinition("MaxHistory", "int", 120, "Max History-Messages pro Request", "number"),
|
|
||||||
new SettingDefinition("ReminderInterval", "int", 30, "System-Prompt Reminder alle N Messages (0 = deaktiviert)", "number"),
|
|
||||||
new SettingDefinition("ExecuteTimeout", "int", 180, "Timeout für Shell-Befehle in Sekunden", "number"),
|
|
||||||
new SettingDefinition("PayloadLogging", "bool", false, "API-Payloads loggen (Debug)", "toggle"),
|
|
||||||
new SettingDefinition("PayloadLoggingGzip", "bool", false, "Payload-Logs als .gz komprimieren", "toggle"),
|
|
||||||
new SettingDefinition("KreaToken", "string", "", "Krea API Token", "password"),
|
|
||||||
new SettingDefinition("KreaApiKey", "string", "", "API-Key für Krea-Endpoints (Automation)", "password"),
|
|
||||||
new SettingDefinition("GitHubToken", "string", "", "GitHub PAT für Key-Scanner", "password"),
|
|
||||||
new SettingDefinition("SerperApiKey", "string", "", "Serper.dev API-Key für Google-Suche (Key-Scanner)", "password"),
|
|
||||||
new SettingDefinition("ProxyUrl", "string", "", "HTTP/SOCKS Proxy für alle Requests (z.B. socks5://127.0.0.1:1080)", "text"),
|
|
||||||
new SettingDefinition("ScanIntervalHours", "int", 0, "Key-Scanner Intervall in Stunden (0 = nur manuell)", "number"),
|
|
||||||
new SettingDefinition("GitHubEventScanner", "bool", false, "GitHub Event Scanner (Echtzeit-Monitoring öffentlicher Pushes)", "toggle"),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static void MapServerSettingsEndpoints(this WebApplication app)
|
public static void MapServerSettingsEndpoints(this WebApplication app)
|
||||||
{
|
{
|
||||||
var contentRoot = app.Environment.ContentRootPath;
|
|
||||||
ProductionConfigPath = Path.Combine(contentRoot, "appsettings.Production.json");
|
|
||||||
FallbackConfigPath = Path.Combine(contentRoot, "appsettings.json");
|
|
||||||
|
|
||||||
app.MapGet("/api/admin/server-settings", HandleGet);
|
app.MapGet("/api/admin/server-settings", HandleGet);
|
||||||
app.MapPut("/api/admin/server-settings", HandleUpdate);
|
app.MapPut("/api/admin/server-settings", HandleUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleGet(HttpContext ctx, IConfiguration config)
|
private static async Task HandleGet(HttpContext ctx, ServerSettingsService settings, IConfiguration config)
|
||||||
{
|
{
|
||||||
if (!ctx.IsAdmin())
|
if (!ctx.IsAdmin())
|
||||||
{
|
{
|
||||||
@@ -57,54 +29,25 @@ public static class ServerSettingsEndpoint
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Production-Config lesen (falls vorhanden)
|
// Port aus appsettings (nicht DB) – als erstes Feld
|
||||||
JsonObject prodConfig = null;
|
var port = config.GetValue<int>("Port", 19000);
|
||||||
if (File.Exists(ProductionConfigPath))
|
var allSettings = new List<object>
|
||||||
{
|
{
|
||||||
var json = await File.ReadAllTextAsync(ProductionConfigPath);
|
new
|
||||||
prodConfig = JsonNode.Parse(json) as JsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback-Config lesen
|
|
||||||
JsonObject fallbackConfig = null;
|
|
||||||
if (File.Exists(FallbackConfigPath))
|
|
||||||
{
|
|
||||||
var json = await File.ReadAllTextAsync(FallbackConfigPath);
|
|
||||||
fallbackConfig = JsonNode.Parse(json) as JsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
var settings = new List<object>();
|
|
||||||
|
|
||||||
foreach (var def in Definitions)
|
|
||||||
{
|
|
||||||
object value = def.DefaultValue;
|
|
||||||
|
|
||||||
// Fallback: appsettings.json
|
|
||||||
if (fallbackConfig != null && fallbackConfig.ContainsKey(def.Key))
|
|
||||||
{
|
{
|
||||||
value = ExtractValue(fallbackConfig[def.Key], def.Type);
|
key = "Port",
|
||||||
|
type = "int",
|
||||||
|
value = (object)port,
|
||||||
|
label = "Server-Port (erfordert Neustart)",
|
||||||
|
inputType = "number",
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
allSettings.AddRange(settings.GetAll());
|
||||||
|
|
||||||
// Override: appsettings.Production.json
|
await ctx.Response.WriteAsJsonAsync(new { settings = allSettings });
|
||||||
if (prodConfig != null && prodConfig.ContainsKey(def.Key))
|
|
||||||
{
|
|
||||||
value = ExtractValue(prodConfig[def.Key], def.Type);
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Add(new
|
|
||||||
{
|
|
||||||
key = def.Key,
|
|
||||||
type = def.Type,
|
|
||||||
value = value,
|
|
||||||
label = def.Label,
|
|
||||||
inputType = def.InputType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.Response.WriteAsJsonAsync(new { settings });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleUpdate(HttpContext ctx)
|
private static async Task HandleUpdate(HttpContext ctx, ServerSettingsService settings, IConfiguration config)
|
||||||
{
|
{
|
||||||
if (!ctx.IsAdmin())
|
if (!ctx.IsAdmin())
|
||||||
{
|
{
|
||||||
@@ -114,75 +57,68 @@ public static class ServerSettingsEndpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
|
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
|
||||||
|
var hint = "";
|
||||||
|
|
||||||
// Bestehende Production-Config laden oder neue erstellen
|
// Port separat behandeln – in appsettings.Production.json speichern
|
||||||
JsonObject prodConfig;
|
if (body.TryGetProperty("Port", out var portProp) && portProp.TryGetInt32(out var newPort))
|
||||||
if (File.Exists(ProductionConfigPath))
|
|
||||||
{
|
{
|
||||||
var existing = await File.ReadAllTextAsync(ProductionConfigPath);
|
var currentPort = config.GetValue<int>("Port", 19000);
|
||||||
prodConfig = JsonNode.Parse(existing) as JsonObject ?? new JsonObject();
|
if (newPort != currentPort && newPort > 0 && newPort <= 65535)
|
||||||
}
|
{
|
||||||
else
|
SavePortToAppSettings(ctx, newPort);
|
||||||
{
|
hint = "Port geändert – Neustart erforderlich!";
|
||||||
prodConfig = new JsonObject();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nur definierte Keys akzeptieren
|
// Alle anderen Settings in DB speichern
|
||||||
foreach (var def in Definitions)
|
foreach (var def in ServerSettingsService.Definitions)
|
||||||
{
|
{
|
||||||
if (!body.TryGetProperty(def.Key, out var prop)) continue;
|
if (!body.TryGetProperty(def.Key, out var prop)) continue;
|
||||||
|
|
||||||
// Alten Wert entfernen falls vorhanden
|
|
||||||
prodConfig.Remove(def.Key);
|
|
||||||
|
|
||||||
switch (def.Type)
|
switch (def.Type)
|
||||||
{
|
{
|
||||||
case "string":
|
case "string":
|
||||||
prodConfig.Add(def.Key, prop.GetString() ?? "");
|
settings.Set(def.Key, prop.GetString() ?? "");
|
||||||
break;
|
break;
|
||||||
case "int":
|
case "int":
|
||||||
prodConfig.Add(def.Key, prop.TryGetInt32(out var i) ? i : (int)def.DefaultValue);
|
settings.SetInt(def.Key, prop.TryGetInt32(out var i) ? i : 0);
|
||||||
break;
|
break;
|
||||||
case "bool":
|
case "bool":
|
||||||
prodConfig.Add(def.Key, prop.ValueKind == JsonValueKind.True);
|
settings.SetBool(def.Key, prop.ValueKind == JsonValueKind.True);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ctx.Response.WriteAsJsonAsync(new { ok = true, hint = string.IsNullOrEmpty(hint) ? "Einstellungen gespeichert" : hint });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SavePortToAppSettings(HttpContext ctx, int port)
|
||||||
|
{
|
||||||
|
// appsettings.Production.json im ContentRootPath (= publish-Verzeichnis)
|
||||||
|
var contentRoot = ctx.RequestServices.GetService(typeof(Microsoft.AspNetCore.Hosting.IWebHostEnvironment)) as Microsoft.AspNetCore.Hosting.IWebHostEnvironment;
|
||||||
|
var path = Path.Combine(contentRoot?.ContentRootPath ?? AppContext.BaseDirectory, "appsettings.Production.json");
|
||||||
|
|
||||||
|
JsonObject obj;
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var existing = File.ReadAllText(path);
|
||||||
|
obj = JsonNode.Parse(existing) as JsonObject ?? new JsonObject();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
obj = new JsonObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
obj = new JsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
obj["Port"] = port;
|
||||||
|
|
||||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||||
var json = prodConfig.ToJsonString(options);
|
File.WriteAllText(path, obj.ToJsonString(options));
|
||||||
await File.WriteAllTextAsync(ProductionConfigPath, json);
|
|
||||||
|
|
||||||
await ctx.Response.WriteAsJsonAsync(new { ok = true, hint = "Die Änderungen erfordern einen Neustart des Servers." });
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object ExtractValue(JsonNode node, string type)
|
|
||||||
{
|
|
||||||
if (node == null) return null;
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case "string": return node.GetValue<string>();
|
|
||||||
case "int": return node.GetValue<int>();
|
|
||||||
case "bool": return node.GetValue<bool>();
|
|
||||||
default: return node.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SettingDefinition
|
|
||||||
{
|
|
||||||
public string Key { get; }
|
|
||||||
public string Type { get; }
|
|
||||||
public object DefaultValue { get; }
|
|
||||||
public string Label { get; }
|
|
||||||
public string InputType { get; }
|
|
||||||
|
|
||||||
public SettingDefinition(string key, string type, object defaultValue, string label, string inputType)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
Type = type;
|
|
||||||
DefaultValue = defaultValue;
|
|
||||||
Label = label;
|
|
||||||
InputType = inputType;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Gott.Services;
|
using Gott.Services;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace Gott.Middleware;
|
namespace Gott.Middleware;
|
||||||
|
|
||||||
public class AuthMiddleware
|
public class AuthMiddleware
|
||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
private readonly RequestDelegate _next;
|
||||||
private readonly string _kreaApiKey;
|
private readonly ServerSettingsService _settings;
|
||||||
|
|
||||||
public AuthMiddleware(RequestDelegate next, IConfiguration config)
|
public AuthMiddleware(RequestDelegate next, ServerSettingsService settings)
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
_kreaApiKey = config["KreaApiKey"] ?? "";
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext ctx, AuthService auth, DatabaseService db)
|
public async Task InvokeAsync(HttpContext ctx, AuthService auth, DatabaseService db)
|
||||||
@@ -34,10 +33,11 @@ public class AuthMiddleware
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Krea API-Key auth (for automation without user context)
|
// Krea API-Key auth (for automation without user context)
|
||||||
if (path.StartsWith("/api/images/") && !string.IsNullOrEmpty(_kreaApiKey))
|
var kreaApiKey = _settings.Get("KreaApiKey");
|
||||||
|
if (path.StartsWith("/api/images/") && !string.IsNullOrEmpty(kreaApiKey))
|
||||||
{
|
{
|
||||||
var apiKey = ctx.Request.Headers["X-Api-Key"].ToString();
|
var apiKey = ctx.Request.Headers["X-Api-Key"].ToString();
|
||||||
if (!string.IsNullOrEmpty(apiKey) && apiKey == _kreaApiKey)
|
if (!string.IsNullOrEmpty(apiKey) && apiKey == kreaApiKey)
|
||||||
{
|
{
|
||||||
await _next(ctx);
|
await _next(ctx);
|
||||||
return;
|
return;
|
||||||
|
|||||||
+18
-17
@@ -16,7 +16,18 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
builder.Services.AddWindowsService();
|
builder.Services.AddWindowsService();
|
||||||
|
|
||||||
var proxyUrl = builder.Configuration["ProxyUrl"];
|
var dataDir = Path.Combine(AppContext.BaseDirectory, "data");
|
||||||
|
Directory.CreateDirectory(dataDir);
|
||||||
|
|
||||||
|
var dbPath = Path.Combine(dataDir, "gott.db");
|
||||||
|
|
||||||
|
// ServerSettingsService – zentrale Settings aus DB
|
||||||
|
var serverSettings = new ServerSettingsService(dbPath);
|
||||||
|
serverSettings.MigrateFromAppSettings(builder.Environment.ContentRootPath);
|
||||||
|
builder.Services.AddSingleton(serverSettings);
|
||||||
|
|
||||||
|
// Proxy (erfordert Neustart – wird nur beim Start gelesen)
|
||||||
|
var proxyUrl = serverSettings.Get("ProxyUrl");
|
||||||
if (!string.IsNullOrWhiteSpace(proxyUrl))
|
if (!string.IsNullOrWhiteSpace(proxyUrl))
|
||||||
{
|
{
|
||||||
builder.Services.AddHttpClient().ConfigureHttpClientDefaults(b =>
|
builder.Services.AddHttpClient().ConfigureHttpClientDefaults(b =>
|
||||||
@@ -31,10 +42,6 @@ else
|
|||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataDir = Path.Combine(AppContext.BaseDirectory, "data");
|
|
||||||
Directory.CreateDirectory(dataDir);
|
|
||||||
|
|
||||||
var dbPath = Path.Combine(dataDir, "gott.db");
|
|
||||||
var authService = new AuthService(dbPath);
|
var authService = new AuthService(dbPath);
|
||||||
var dbService = new DatabaseService(dbPath);
|
var dbService = new DatabaseService(dbPath);
|
||||||
dbService.EnsureDefaultUser(authService);
|
dbService.EnsureDefaultUser(authService);
|
||||||
@@ -50,11 +57,9 @@ builder.Services.AddSingleton(memoryService);
|
|||||||
builder.Services.AddSingleton(skillService);
|
builder.Services.AddSingleton(skillService);
|
||||||
|
|
||||||
// Shared Services
|
// Shared Services
|
||||||
var toolService = new ToolService(builder.Configuration);
|
var toolService = new ToolService(serverSettings);
|
||||||
builder.Services.AddSingleton(toolService);
|
builder.Services.AddSingleton(toolService);
|
||||||
var maxHistory = builder.Configuration.GetValue<int>("MaxHistory", 120);
|
var chatService = new ChatService(dbService, toolService, memoryService, skillService, serverSettings);
|
||||||
var reminderInterval = builder.Configuration.GetValue<int>("ReminderInterval", 30);
|
|
||||||
var chatService = new ChatService(dbService, toolService, memoryService, skillService, maxHistory, reminderInterval);
|
|
||||||
builder.Services.AddSingleton(chatService);
|
builder.Services.AddSingleton(chatService);
|
||||||
|
|
||||||
// Embedding-Services (lokal, ONNX)
|
// Embedding-Services (lokal, ONNX)
|
||||||
@@ -67,14 +72,12 @@ builder.Services.AddSingleton(embeddingBgService);
|
|||||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<Gott.Services.EmbeddingBackgroundService>());
|
builder.Services.AddHostedService(sp => sp.GetRequiredService<Gott.Services.EmbeddingBackgroundService>());
|
||||||
|
|
||||||
// AnthropicService (Singleton – Token wird pro Request vom User aufgelöst)
|
// AnthropicService (Singleton – Token wird pro Request vom User aufgelöst)
|
||||||
var payloadLogging = builder.Configuration.GetValue<bool>("PayloadLogging", false);
|
|
||||||
var payloadLoggingGzip = builder.Configuration.GetValue<bool>("PayloadLoggingGzip", false);
|
|
||||||
builder.Services.AddSingleton<AnthropicService>(sp =>
|
builder.Services.AddSingleton<AnthropicService>(sp =>
|
||||||
{
|
{
|
||||||
var svc = new AnthropicService(
|
var svc = new AnthropicService(
|
||||||
sp.GetRequiredService<IHttpClientFactory>(),
|
sp.GetRequiredService<IHttpClientFactory>(),
|
||||||
toolService, dbService, memoryService,
|
toolService, dbService, memoryService,
|
||||||
payloadLogging, payloadLoggingGzip,
|
serverSettings,
|
||||||
vectorSearch: vectorSearchService,
|
vectorSearch: vectorSearchService,
|
||||||
embeddingBg: embeddingBgService);
|
embeddingBg: embeddingBgService);
|
||||||
svc.SetChatService(chatService);
|
svc.SetChatService(chatService);
|
||||||
@@ -83,21 +86,19 @@ builder.Services.AddSingleton<AnthropicService>(sp =>
|
|||||||
|
|
||||||
// OpenAI-kompatibler Service (Nvidia etc.)
|
// OpenAI-kompatibler Service (Nvidia etc.)
|
||||||
builder.Services.AddSingleton<OpenAiService>(sp =>
|
builder.Services.AddSingleton<OpenAiService>(sp =>
|
||||||
new OpenAiService(sp.GetRequiredService<IHttpClientFactory>(), dbService, sp.GetRequiredService<ToolService>(), builder.Configuration, dataDir));
|
new OpenAiService(sp.GetRequiredService<IHttpClientFactory>(), dbService, sp.GetRequiredService<ToolService>(), serverSettings, dataDir));
|
||||||
|
|
||||||
// Telegram Bot (pro Session – Token aus Session-Config)
|
// Telegram Bot (pro Session – Token aus Session-Config)
|
||||||
var maxIterations = builder.Configuration.GetValue<int>("MaxIterations", 100);
|
|
||||||
builder.Services.AddSingleton<TelegramService>(sp =>
|
builder.Services.AddSingleton<TelegramService>(sp =>
|
||||||
new TelegramService(
|
new TelegramService(
|
||||||
sp.GetRequiredService<IHttpClientFactory>(),
|
sp.GetRequiredService<IHttpClientFactory>(),
|
||||||
dbService, chatService,
|
dbService, chatService,
|
||||||
sp.GetRequiredService<AnthropicService>(),
|
sp.GetRequiredService<AnthropicService>(),
|
||||||
maxIterations));
|
serverSettings));
|
||||||
|
|
||||||
// Krea Image Generation
|
// Krea Image Generation
|
||||||
var kreaToken = builder.Configuration["KreaToken"] ?? "";
|
|
||||||
builder.Services.AddSingleton(sp =>
|
builder.Services.AddSingleton(sp =>
|
||||||
new KreaService(sp.GetRequiredService<IHttpClientFactory>(), kreaToken, dataDir));
|
new KreaService(sp.GetRequiredService<IHttpClientFactory>(), serverSettings, dataDir));
|
||||||
|
|
||||||
// Cronjob-Service (BackgroundService)
|
// Cronjob-Service (BackgroundService)
|
||||||
builder.Services.AddSingleton<CronjobService>();
|
builder.Services.AddSingleton<CronjobService>();
|
||||||
|
|||||||
@@ -66,16 +66,7 @@ Beim ersten Start wird `appsettings.json` mit Standardwerten verwendet. Für eig
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Port": 19000,
|
"Port": 19000
|
||||||
"MaxIterations": 100,
|
|
||||||
"MaxHistory": 120,
|
|
||||||
"ReminderInterval": 30,
|
|
||||||
"ExecuteTimeout": 180,
|
|
||||||
"PayloadLogging": false,
|
|
||||||
"PayloadLoggingGzip": false,
|
|
||||||
"KreaToken": "",
|
|
||||||
"KreaApiKey": "",
|
|
||||||
"GeminiApiKey": ""
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -19,21 +19,19 @@ public class AnthropicService
|
|||||||
private readonly ToolService _toolService;
|
private readonly ToolService _toolService;
|
||||||
private readonly DatabaseService _db;
|
private readonly DatabaseService _db;
|
||||||
private readonly MemoryService _memoryService;
|
private readonly MemoryService _memoryService;
|
||||||
private readonly bool _payloadLogging;
|
private readonly ServerSettingsService _settings;
|
||||||
private readonly bool _payloadLoggingGzip;
|
|
||||||
private readonly VectorSearchService _vectorSearch;
|
private readonly VectorSearchService _vectorSearch;
|
||||||
private readonly EmbeddingBackgroundService _embeddingBg;
|
private readonly EmbeddingBackgroundService _embeddingBg;
|
||||||
private ChatService _chatService;
|
private ChatService _chatService;
|
||||||
private TelegramService _telegramService;
|
private TelegramService _telegramService;
|
||||||
|
|
||||||
public AnthropicService(IHttpClientFactory httpFactory, ToolService toolService, DatabaseService db, MemoryService memoryService = null, bool payloadLogging = false, bool payloadLoggingGzip = false, VectorSearchService vectorSearch = null, EmbeddingBackgroundService embeddingBg = null)
|
public AnthropicService(IHttpClientFactory httpFactory, ToolService toolService, DatabaseService db, MemoryService memoryService = null, ServerSettingsService settings = null, VectorSearchService vectorSearch = null, EmbeddingBackgroundService embeddingBg = null)
|
||||||
{
|
{
|
||||||
_httpFactory = httpFactory;
|
_httpFactory = httpFactory;
|
||||||
_toolService = toolService;
|
_toolService = toolService;
|
||||||
_db = db;
|
_db = db;
|
||||||
_memoryService = memoryService;
|
_memoryService = memoryService;
|
||||||
_payloadLogging = payloadLogging;
|
_settings = settings;
|
||||||
_payloadLoggingGzip = payloadLoggingGzip;
|
|
||||||
_vectorSearch = vectorSearch;
|
_vectorSearch = vectorSearch;
|
||||||
_embeddingBg = embeddingBg;
|
_embeddingBg = embeddingBg;
|
||||||
}
|
}
|
||||||
@@ -1201,7 +1199,7 @@ public class AnthropicService
|
|||||||
|
|
||||||
private void LogPayload(string json, string sessionId, string chainId, int iteration)
|
private void LogPayload(string json, string sessionId, string chainId, int iteration)
|
||||||
{
|
{
|
||||||
if (!_payloadLogging) return;
|
if (_settings == null || !_settings.GetBool("PayloadLogging", false)) return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var logsDir = Path.Combine(AppContext.BaseDirectory, "data", "logs");
|
var logsDir = Path.Combine(AppContext.BaseDirectory, "data", "logs");
|
||||||
@@ -1209,7 +1207,7 @@ public class AnthropicService
|
|||||||
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
var baseName = $"{timestamp}-{sessionId}-{chainId}-iter{iteration}-payload";
|
var baseName = $"{timestamp}-{sessionId}-{chainId}-iter{iteration}-payload";
|
||||||
|
|
||||||
if (_payloadLoggingGzip)
|
if (_settings?.GetBool("PayloadLoggingGzip", false) ?? false)
|
||||||
{
|
{
|
||||||
var filePath = Path.Combine(logsDir, baseName + ".json.gz");
|
var filePath = Path.Combine(logsDir, baseName + ".json.gz");
|
||||||
using var fs = File.Create(filePath);
|
using var fs = File.Create(filePath);
|
||||||
|
|||||||
+10
-9
@@ -14,20 +14,21 @@ public class ChatService
|
|||||||
private readonly ToolService _toolService;
|
private readonly ToolService _toolService;
|
||||||
private readonly MemoryService _memoryService;
|
private readonly MemoryService _memoryService;
|
||||||
private readonly SkillService _skillService;
|
private readonly SkillService _skillService;
|
||||||
private readonly int _maxHistory;
|
private readonly ServerSettingsService _settings;
|
||||||
private readonly int _reminderInterval;
|
|
||||||
|
|
||||||
public ChatService(DatabaseService db, ToolService toolService, MemoryService memoryService, SkillService skillService,
|
public ChatService(DatabaseService db, ToolService toolService, MemoryService memoryService, SkillService skillService,
|
||||||
int maxHistory = 120, int reminderInterval = 30)
|
ServerSettingsService settings)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_toolService = toolService;
|
_toolService = toolService;
|
||||||
_memoryService = memoryService;
|
_memoryService = memoryService;
|
||||||
_skillService = skillService;
|
_skillService = skillService;
|
||||||
_maxHistory = maxHistory;
|
_settings = settings;
|
||||||
_reminderInterval = reminderInterval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int MaxHistory => _settings.GetInt("MaxHistory", 120);
|
||||||
|
private int ReminderInterval => _settings.GetInt("ReminderInterval", 30);
|
||||||
|
|
||||||
public static string ResolveModelId(string model)
|
public static string ResolveModelId(string model)
|
||||||
{
|
{
|
||||||
// OpenAI-kompatible Provider werden unverändert durchgereicht (Format: "provider:model")
|
// OpenAI-kompatible Provider werden unverändert durchgereicht (Format: "provider:model")
|
||||||
@@ -487,9 +488,9 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
|||||||
|
|
||||||
// History auf maximal N Messages begrenzen (älteste entfernen)
|
// History auf maximal N Messages begrenzen (älteste entfernen)
|
||||||
// WICHTIG: Nicht mitten in einem tool_use/tool_result-Paar abschneiden!
|
// WICHTIG: Nicht mitten in einem tool_use/tool_result-Paar abschneiden!
|
||||||
if (history.Count > _maxHistory)
|
if (history.Count > MaxHistory)
|
||||||
{
|
{
|
||||||
int removeCount = history.Count - _maxHistory;
|
int removeCount = history.Count - MaxHistory;
|
||||||
// Nach vorne korrigieren bis wir an einer sicheren Stelle sind:
|
// Nach vorne korrigieren bis wir an einer sicheren Stelle sind:
|
||||||
// Nicht abschneiden wenn die erste verbleibende Message ein tool_result ist
|
// Nicht abschneiden wenn die erste verbleibende Message ein tool_result ist
|
||||||
// oder die letzte entfernte Message ein assistant mit tool_use ist
|
// oder die letzte entfernte Message ein assistant mit tool_use ist
|
||||||
@@ -550,7 +551,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
|||||||
|
|
||||||
// Reminder einfügen: alle ~30 user/assistant-Paare ein Reminder-Paar einfügen
|
// Reminder einfügen: alle ~30 user/assistant-Paare ein Reminder-Paar einfügen
|
||||||
var reminder = SystemPromptBuilder.BuildReminder(promptBlocks);
|
var reminder = SystemPromptBuilder.BuildReminder(promptBlocks);
|
||||||
if (!string.IsNullOrWhiteSpace(reminder) && _reminderInterval > 0 && history.Count > _reminderInterval)
|
if (!string.IsNullOrWhiteSpace(reminder) && ReminderInterval > 0 && history.Count > ReminderInterval)
|
||||||
{
|
{
|
||||||
var reminderUser = new Dictionary<string, object>
|
var reminderUser = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
@@ -569,7 +570,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
|||||||
for (int i = 0; i < history.Count; i++)
|
for (int i = 0; i < history.Count; i++)
|
||||||
{
|
{
|
||||||
messageCount++;
|
messageCount++;
|
||||||
if (messageCount > 0 && messageCount % _reminderInterval == 0)
|
if (messageCount > 0 && messageCount % ReminderInterval == 0)
|
||||||
{
|
{
|
||||||
// Position nach dem aktuellen Element – aber nur einfügen wenn es eine
|
// Position nach dem aktuellen Element – aber nur einfügen wenn es eine
|
||||||
// assistant-Message ist (damit user/assistant-Alternierung nicht bricht)
|
// assistant-Message ist (damit user/assistant-Alternierung nicht bricht)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Gott.Models;
|
using Gott.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -23,8 +22,10 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
private readonly DatabaseService _db;
|
private readonly DatabaseService _db;
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
private readonly ILogger<GitHubEventScannerService> _logger;
|
private readonly ILogger<GitHubEventScannerService> _logger;
|
||||||
private readonly string _githubToken;
|
private readonly ServerSettingsService _settings;
|
||||||
private readonly bool _enabled;
|
|
||||||
|
private string _githubToken => _settings.Get("GitHubToken");
|
||||||
|
private bool _enabled => _settings.GetBool("GitHubEventScanner", false);
|
||||||
|
|
||||||
private const int PollIntervalMs = 30_000; // 30 Sekunden (häufiger pollen, weniger verpassen)
|
private const int PollIntervalMs = 30_000; // 30 Sekunden (häufiger pollen, weniger verpassen)
|
||||||
private const int MaxCommitsPerPoll = 30; // Max Commit-Diffs pro Poll-Zyklus
|
private const int MaxCommitsPerPoll = 30; // Max Commit-Diffs pro Poll-Zyklus
|
||||||
@@ -90,31 +91,18 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
public GitHubEventScannerService(
|
public GitHubEventScannerService(
|
||||||
DatabaseService db,
|
DatabaseService db,
|
||||||
IHttpClientFactory httpFactory,
|
IHttpClientFactory httpFactory,
|
||||||
IConfiguration config,
|
ServerSettingsService settings,
|
||||||
ILogger<GitHubEventScannerService> logger)
|
ILogger<GitHubEventScannerService> logger)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_httpFactory = httpFactory;
|
_httpFactory = httpFactory;
|
||||||
|
_settings = settings;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_githubToken = config["GitHubToken"] ?? "";
|
|
||||||
_enabled = config.GetValue<bool>("GitHubEventScanner", false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
if (!_enabled)
|
_logger.LogInformation("GitHubEventScannerService bereit (Enabled wird dynamisch aus Settings gelesen)");
|
||||||
{
|
|
||||||
_logger.LogInformation("GitHubEventScannerService deaktiviert (GitHubEventScanner=false)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(_githubToken))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("GitHubEventScannerService deaktiviert (kein GitHubToken konfiguriert)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("GitHubEventScannerService gestartet (Poll-Intervall: {Interval}s)", PollIntervalMs / 1000);
|
|
||||||
|
|
||||||
// Kurze Verzögerung nach App-Start
|
// Kurze Verzögerung nach App-Start
|
||||||
await Task.Delay(5_000, stoppingToken);
|
await Task.Delay(5_000, stoppingToken);
|
||||||
@@ -123,6 +111,13 @@ public class GitHubEventScannerService : BackgroundService
|
|||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
// Dynamisch prüfen ob aktiviert
|
||||||
|
if (!_enabled || string.IsNullOrWhiteSpace(_githubToken))
|
||||||
|
{
|
||||||
|
lock (_lock) _isRunning = false;
|
||||||
|
await Task.Delay(10_000, stoppingToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lock (_lock) _isRunning = true;
|
lock (_lock) _isRunning = true;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Gott.Models;
|
using Gott.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -20,9 +19,11 @@ public class KeyScannerService : BackgroundService
|
|||||||
private readonly DatabaseService _db;
|
private readonly DatabaseService _db;
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
private readonly ILogger<KeyScannerService> _logger;
|
private readonly ILogger<KeyScannerService> _logger;
|
||||||
private readonly string _githubToken;
|
private readonly ServerSettingsService _settings;
|
||||||
private readonly string _serperApiKey;
|
|
||||||
private readonly int _intervalHours;
|
private string _githubToken => _settings.Get("GitHubToken");
|
||||||
|
private string _serperApiKey => _settings.Get("SerperApiKey");
|
||||||
|
private int _intervalHours => _settings.GetInt("ScanIntervalHours", 0);
|
||||||
|
|
||||||
// Status-Tracking
|
// Status-Tracking
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
@@ -75,15 +76,13 @@ public class KeyScannerService : BackgroundService
|
|||||||
public KeyScannerService(
|
public KeyScannerService(
|
||||||
DatabaseService db,
|
DatabaseService db,
|
||||||
IHttpClientFactory httpFactory,
|
IHttpClientFactory httpFactory,
|
||||||
IConfiguration config,
|
ServerSettingsService settings,
|
||||||
ILogger<KeyScannerService> logger)
|
ILogger<KeyScannerService> logger)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_httpFactory = httpFactory;
|
_httpFactory = httpFactory;
|
||||||
|
_settings = settings;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_githubToken = config["GitHubToken"] ?? "";
|
|
||||||
_serperApiKey = config["SerperApiKey"] ?? "";
|
|
||||||
_intervalHours = config.GetValue<int>("ScanIntervalHours", 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
@@ -163,7 +162,6 @@ public class KeyScannerService : BackgroundService
|
|||||||
var modeLabel = modeLabels.ContainsKey(mode) ? modeLabels[mode] : "Full";
|
var modeLabel = modeLabels.ContainsKey(mode) ? modeLabels[mode] : "Full";
|
||||||
AddLog("scan", modeLabel + "-Scan gestartet");
|
AddLog("scan", modeLabel + "-Scan gestartet");
|
||||||
var newKeysFound = 0;
|
var newKeysFound = 0;
|
||||||
var checkedCount = 0;
|
|
||||||
var totalFound = 0;
|
var totalFound = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -230,21 +228,10 @@ public class KeyScannerService : BackgroundService
|
|||||||
}
|
}
|
||||||
AddLog("scan", newKeysFound + " neue Key(s) gespeichert");
|
AddLog("scan", newKeysFound + " neue Key(s) gespeichert");
|
||||||
|
|
||||||
// Phase 3: Alle unchecked Keys prüfen
|
|
||||||
var allScanned = (await _db.GetScannedKeys()).ToList();
|
|
||||||
var toCheck = allScanned.Where(k => k.Status == "unchecked").ToList();
|
|
||||||
foreach (var sk in toCheck)
|
|
||||||
{
|
|
||||||
ct.ThrowIfCancellationRequested();
|
|
||||||
await CheckAndUpdateKey(sk);
|
|
||||||
checkedCount++;
|
|
||||||
await Task.Delay(500, ct); // Rate-Limit schonen
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_lastRun = DateTime.UtcNow.ToString("o");
|
_lastRun = DateTime.UtcNow.ToString("o");
|
||||||
_lastResult = $"{modeLabel}: {totalFound} gefunden, {newKeysFound} neu, {checkedCount} geprüft";
|
_lastResult = $"{modeLabel}: {totalFound} gefunden, {newKeysFound} neu";
|
||||||
}
|
}
|
||||||
|
|
||||||
AddLog("scan", "Scan abgeschlossen – " + _lastResult);
|
AddLog("scan", "Scan abgeschlossen – " + _lastResult);
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ namespace Gott.Services
|
|||||||
public class KreaService
|
public class KreaService
|
||||||
{
|
{
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
private readonly string _token;
|
private readonly ServerSettingsService _settings;
|
||||||
private readonly string _imagesDir;
|
private readonly string _imagesDir;
|
||||||
|
|
||||||
|
private string _token => _settings.Get("KreaToken");
|
||||||
|
|
||||||
private const string BASE_URL = "https://api.krea.ai";
|
private const string BASE_URL = "https://api.krea.ai";
|
||||||
|
|
||||||
public static readonly Dictionary<string, string> IMAGE_MODELS = new Dictionary<string, string>
|
public static readonly Dictionary<string, string> IMAGE_MODELS = new Dictionary<string, string>
|
||||||
@@ -47,10 +49,10 @@ namespace Gott.Services
|
|||||||
|
|
||||||
public bool IsConfigured => !string.IsNullOrWhiteSpace(_token);
|
public bool IsConfigured => !string.IsNullOrWhiteSpace(_token);
|
||||||
|
|
||||||
public KreaService(IHttpClientFactory httpFactory, string token, string dataDir)
|
public KreaService(IHttpClientFactory httpFactory, ServerSettingsService settings, string dataDir)
|
||||||
{
|
{
|
||||||
_httpFactory = httpFactory;
|
_httpFactory = httpFactory;
|
||||||
_token = token;
|
_settings = settings;
|
||||||
_imagesDir = Path.Combine(dataDir, "images");
|
_imagesDir = Path.Combine(dataDir, "images");
|
||||||
Directory.CreateDirectory(_imagesDir);
|
Directory.CreateDirectory(_imagesDir);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,17 +156,20 @@ public class OpenAiService
|
|||||||
private List<ProviderModel> _openaiModels = new List<ProviderModel>();
|
private List<ProviderModel> _openaiModels = new List<ProviderModel>();
|
||||||
private readonly string _cacheFile;
|
private readonly string _cacheFile;
|
||||||
|
|
||||||
public OpenAiService(IHttpClientFactory httpFactory, DatabaseService db, ToolService toolService, Microsoft.Extensions.Configuration.IConfiguration configuration, string dataDir)
|
private readonly ServerSettingsService _settings;
|
||||||
|
|
||||||
|
public OpenAiService(IHttpClientFactory httpFactory, DatabaseService db, ToolService toolService, ServerSettingsService settings, string dataDir)
|
||||||
{
|
{
|
||||||
_httpFactory = httpFactory;
|
_httpFactory = httpFactory;
|
||||||
_db = db;
|
_db = db;
|
||||||
_toolService = toolService;
|
_toolService = toolService;
|
||||||
|
_settings = settings;
|
||||||
_cacheFile = Path.Combine(dataDir, "models_cache.json");
|
_cacheFile = Path.Combine(dataDir, "models_cache.json");
|
||||||
Providers = new Dictionary<string, (string BaseUrl, string ApiKey)>
|
Providers = new Dictionary<string, (string BaseUrl, string ApiKey)>
|
||||||
{
|
{
|
||||||
["nvidia"] = ("https://g4f.space/api/nvidia/v1", ""),
|
["nvidia"] = ("https://g4f.space/api/nvidia/v1", ""),
|
||||||
["pollinations"] = ("https://text.pollinations.ai/openai", ""),
|
["pollinations"] = ("https://text.pollinations.ai/openai", ""),
|
||||||
["gemini"] = ("https://generativelanguage.googleapis.com/v1beta/openai", configuration["GeminiApiKey"] ?? ""),
|
["gemini"] = ("https://generativelanguage.googleapis.com/v1beta/openai", ""), // Key kommt dynamisch aus Settings
|
||||||
["openai"] = ("https://api.openai.com/v1", "") // Key kommt dynamisch aus DB
|
["openai"] = ("https://api.openai.com/v1", "") // Key kommt dynamisch aus DB
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -209,7 +212,7 @@ public class OpenAiService
|
|||||||
_nvidiaModels = await FetchModels("https://g4f.space/api/nvidia/models", "Nvidia");
|
_nvidiaModels = await FetchModels("https://g4f.space/api/nvidia/models", "Nvidia");
|
||||||
_pollinationsModels = await FetchModels("https://text.pollinations.ai/models", "Pollinations");
|
_pollinationsModels = await FetchModels("https://text.pollinations.ai/models", "Pollinations");
|
||||||
|
|
||||||
var geminiApiKey = Providers["gemini"].ApiKey;
|
var geminiApiKey = _settings.Get("GeminiApiKey");
|
||||||
if (!string.IsNullOrEmpty(geminiApiKey))
|
if (!string.IsNullOrEmpty(geminiApiKey))
|
||||||
_geminiModels = await FetchModels("https://generativelanguage.googleapis.com/v1beta/openai/models", "Gemini", geminiApiKey);
|
_geminiModels = await FetchModels("https://generativelanguage.googleapis.com/v1beta/openai/models", "Gemini", geminiApiKey);
|
||||||
else
|
else
|
||||||
@@ -363,6 +366,10 @@ public class OpenAiService
|
|||||||
if (!string.IsNullOrEmpty(apiKeyOverride))
|
if (!string.IsNullOrEmpty(apiKeyOverride))
|
||||||
providerConfig = (providerConfig.BaseUrl, apiKeyOverride);
|
providerConfig = (providerConfig.BaseUrl, apiKeyOverride);
|
||||||
|
|
||||||
|
// Gemini: Key dynamisch aus ServerSettings holen (falls nicht per Override gesetzt)
|
||||||
|
if (provider == "gemini" && string.IsNullOrEmpty(providerConfig.ApiKey))
|
||||||
|
providerConfig = (providerConfig.BaseUrl, _settings.Get("GeminiApiKey"));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// OpenAI-kompatibles Messages-Format aufbauen
|
// OpenAI-kompatibles Messages-Format aufbauen
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
namespace Gott.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zentraler Settings-Service. Liest/schreibt aus der DB-Tabelle 'server_settings'.
|
||||||
|
/// In-Memory-Cache für schnellen Zugriff. Kein Neustart bei Änderungen nötig.
|
||||||
|
/// </summary>
|
||||||
|
public class ServerSettingsService
|
||||||
|
{
|
||||||
|
private readonly string _dbPath;
|
||||||
|
private readonly Dictionary<string, string> _cache = new Dictionary<string, string>();
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
|
// Definiert alle Settings mit Typ, Default und Beschreibung
|
||||||
|
public static readonly List<SettingDefinition> Definitions = new List<SettingDefinition>
|
||||||
|
{
|
||||||
|
new SettingDefinition( "ProxyUrl", "string", "", "HTTP/SOCKS Proxy (erfordert Neustart)", "text" ),
|
||||||
|
new SettingDefinition( "MaxIterations", "int", "100", "Max Tool-Loop Iterationen pro Chat", "number" ),
|
||||||
|
new SettingDefinition( "MaxHistory", "int", "120", "Max History-Messages pro Request", "number" ),
|
||||||
|
new SettingDefinition( "ReminderInterval", "int", "30", "System-Prompt Reminder alle N Messages (0 = deaktiviert)", "number" ),
|
||||||
|
new SettingDefinition( "ExecuteTimeout", "int", "180", "Timeout für Shell-Befehle in Sekunden", "number" ),
|
||||||
|
new SettingDefinition( "PayloadLogging", "bool", "false", "API-Payloads loggen (Debug)", "toggle" ),
|
||||||
|
new SettingDefinition( "PayloadLoggingGzip", "bool", "false", "Payload-Logs als .gz komprimieren", "toggle" ),
|
||||||
|
new SettingDefinition( "KreaToken", "string", "", "Krea API Token", "password" ),
|
||||||
|
new SettingDefinition( "KreaApiKey", "string", "", "API-Key für Krea-Endpoints (Automation)", "password" ),
|
||||||
|
new SettingDefinition( "GeminiApiKey", "string", "", "Gemini API-Key (für Gemini-Provider im Chat)", "password" ),
|
||||||
|
new SettingDefinition( "GitHubToken", "string", "", "GitHub PAT für Key-Scanner und Code-Search Tool", "password" ),
|
||||||
|
new SettingDefinition( "SerperApiKey", "string", "", "Serper.dev API-Key für Google-Suche (Key-Scanner + Tool)", "password" ),
|
||||||
|
new SettingDefinition( "ScanIntervalHours", "int", "0", "Key-Scanner Intervall in Stunden (0 = nur manuell)", "number" ),
|
||||||
|
new SettingDefinition( "GitHubEventScanner", "bool", "false", "GitHub Event Scanner (Echtzeit-Monitoring öffentlicher Pushes)", "toggle" ),
|
||||||
|
};
|
||||||
|
|
||||||
|
public ServerSettingsService(string dbPath)
|
||||||
|
{
|
||||||
|
_dbPath = dbPath;
|
||||||
|
EnsureTable();
|
||||||
|
LoadCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureTable()
|
||||||
|
{
|
||||||
|
using var conn = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
|
conn.Open();
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
CREATE TABLE IF NOT EXISTS server_settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL DEFAULT '',
|
||||||
|
type TEXT NOT NULL DEFAULT 'string'
|
||||||
|
)";
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadCache()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
|
using var conn = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
|
conn.Open();
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = "SELECT key, value FROM server_settings";
|
||||||
|
using var reader = cmd.ExecuteReader();
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
_cache[reader.GetString(0)] = reader.GetString(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Migriert bestehende Werte aus appsettings.Production.json in die DB (einmalig).
|
||||||
|
/// Nur Settings die noch NICHT in der DB sind werden übernommen.
|
||||||
|
/// </summary>
|
||||||
|
public void MigrateFromAppSettings(string contentRootPath)
|
||||||
|
{
|
||||||
|
var prodPath = Path.Combine(contentRootPath, "appsettings.Production.json");
|
||||||
|
if (!File.Exists(prodPath)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(prodPath);
|
||||||
|
var obj = JsonNode.Parse(json) as JsonObject;
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
// Port bleibt in appsettings – nicht migrieren
|
||||||
|
var skipKeys = new HashSet<string> { "Port", "Logging", "AllowedHosts" };
|
||||||
|
|
||||||
|
foreach (var def in Definitions)
|
||||||
|
{
|
||||||
|
if (skipKeys.Contains(def.Key)) continue;
|
||||||
|
if (!obj.ContainsKey(def.Key)) continue;
|
||||||
|
|
||||||
|
// Nur migrieren wenn nicht schon in DB
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_cache.ContainsKey(def.Key)) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = obj[def.Key];
|
||||||
|
string value;
|
||||||
|
switch (def.Type)
|
||||||
|
{
|
||||||
|
case "bool":
|
||||||
|
value = (node?.GetValue<bool>() ?? false) ? "true" : "false";
|
||||||
|
break;
|
||||||
|
case "int":
|
||||||
|
value = (node?.GetValue<int>() ?? 0).ToString();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = node?.GetValue<string>() ?? "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default-Werte nicht migrieren (kein Müll in DB)
|
||||||
|
if (value == def.DefaultValue) continue;
|
||||||
|
|
||||||
|
Set(def.Key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fehler bei Migration ignorieren – Settings sind halt Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Getter ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public string Get(string key, string defaultValue = "")
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return _cache.TryGetValue(key, out var val) ? val : defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetInt(string key, int defaultValue = 0)
|
||||||
|
{
|
||||||
|
var raw = Get(key, null);
|
||||||
|
return raw != null && int.TryParse(raw, out var v) ? v : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetBool(string key, bool defaultValue = false)
|
||||||
|
{
|
||||||
|
var raw = Get(key, null);
|
||||||
|
if (raw == null) return defaultValue;
|
||||||
|
return raw.Equals("true", StringComparison.OrdinalIgnoreCase) || raw == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Setter ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public void Set(string key, string value)
|
||||||
|
{
|
||||||
|
// Typ aus Definitions holen
|
||||||
|
var def = Definitions.Find(d => d.Key == key);
|
||||||
|
var type = def?.Type ?? "string";
|
||||||
|
|
||||||
|
using var conn = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
|
conn.Open();
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
INSERT INTO server_settings (key, value, type) VALUES (@key, @value, @type)
|
||||||
|
ON CONFLICT(key) DO UPDATE SET value = @value, type = @type";
|
||||||
|
cmd.Parameters.AddWithValue("@key", key);
|
||||||
|
cmd.Parameters.AddWithValue("@value", value ?? "");
|
||||||
|
cmd.Parameters.AddWithValue("@type", type);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_cache[key] = value ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInt(string key, int value) => Set(key, value.ToString());
|
||||||
|
public void SetBool(string key, bool value) => Set(key, value ? "true" : "false");
|
||||||
|
|
||||||
|
// ─── Bulk-Operationen (für Endpoint) ───────────────────────
|
||||||
|
|
||||||
|
public List<object> GetAll()
|
||||||
|
{
|
||||||
|
var result = new List<object>();
|
||||||
|
foreach (var def in Definitions)
|
||||||
|
{
|
||||||
|
object value;
|
||||||
|
switch (def.Type)
|
||||||
|
{
|
||||||
|
case "int":
|
||||||
|
value = GetInt(def.Key, int.TryParse(def.DefaultValue, out var dv) ? dv : 0);
|
||||||
|
break;
|
||||||
|
case "bool":
|
||||||
|
value = GetBool(def.Key, def.DefaultValue.Equals("true", StringComparison.OrdinalIgnoreCase));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = Get(def.Key, def.DefaultValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(new
|
||||||
|
{
|
||||||
|
key = def.Key,
|
||||||
|
type = def.Type,
|
||||||
|
value = value,
|
||||||
|
label = def.Label,
|
||||||
|
inputType = def.InputType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Definition ────────────────────────────────────────────
|
||||||
|
|
||||||
|
public class SettingDefinition
|
||||||
|
{
|
||||||
|
public string Key { get; }
|
||||||
|
public string Type { get; }
|
||||||
|
public string DefaultValue { get; }
|
||||||
|
public string Label { get; }
|
||||||
|
public string InputType { get; }
|
||||||
|
|
||||||
|
public SettingDefinition(string key, string type, string defaultValue, string label, string inputType)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Type = type;
|
||||||
|
DefaultValue = defaultValue;
|
||||||
|
Label = label;
|
||||||
|
InputType = inputType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,20 +15,20 @@ public class TelegramService
|
|||||||
private readonly DatabaseService _db;
|
private readonly DatabaseService _db;
|
||||||
private readonly ChatService _chatService;
|
private readonly ChatService _chatService;
|
||||||
private readonly AnthropicService _anthropic;
|
private readonly AnthropicService _anthropic;
|
||||||
private readonly int _maxIterations;
|
private readonly ServerSettingsService _settings;
|
||||||
|
|
||||||
public TelegramService(
|
public TelegramService(
|
||||||
IHttpClientFactory httpFactory,
|
IHttpClientFactory httpFactory,
|
||||||
DatabaseService db,
|
DatabaseService db,
|
||||||
ChatService chatService,
|
ChatService chatService,
|
||||||
AnthropicService anthropic,
|
AnthropicService anthropic,
|
||||||
int maxIterations = 100)
|
ServerSettingsService settings)
|
||||||
{
|
{
|
||||||
_httpFactory = httpFactory;
|
_httpFactory = httpFactory;
|
||||||
_db = db;
|
_db = db;
|
||||||
_chatService = chatService;
|
_chatService = chatService;
|
||||||
_anthropic = anthropic;
|
_anthropic = anthropic;
|
||||||
_maxIterations = maxIterations;
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Webhook-Update verarbeiten ──
|
// ── Webhook-Update verarbeiten ──
|
||||||
@@ -161,7 +161,7 @@ public class TelegramService
|
|||||||
|
|
||||||
private async Task HandleMessage(long chatId, string userText, string botToken, string botUserId, SessionRecord session)
|
private async Task HandleMessage(long chatId, string userText, string botToken, string botUserId, SessionRecord session)
|
||||||
{
|
{
|
||||||
var ctx = await _chatService.PrepareContext(session.SessionId, botUserId, _maxIterations);
|
var ctx = await _chatService.PrepareContext(session.SessionId, botUserId, _settings.GetInt("MaxIterations", 100));
|
||||||
if (ctx == null)
|
if (ctx == null)
|
||||||
{
|
{
|
||||||
await SendMessage(chatId, botToken, "❌ Kein Agent konfiguriert. Bitte weise der Session im Web-UI einen Agent zu.");
|
await SendMessage(chatId, botToken, "❌ Kein Agent konfiguriert. Bitte weise der Session im Web-UI einen Agent zu.");
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using System.Text.Json;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.FileSystemGlobbing;
|
using Microsoft.Extensions.FileSystemGlobbing;
|
||||||
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
|
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
|
||||||
|
|
||||||
@@ -18,12 +17,12 @@ namespace Gott.Services;
|
|||||||
|
|
||||||
public class ToolService
|
public class ToolService
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _config;
|
private readonly ServerSettingsService _settings;
|
||||||
private IHttpClientFactory _httpFactory;
|
private IHttpClientFactory _httpFactory;
|
||||||
|
|
||||||
public ToolService(IConfiguration config)
|
public ToolService(ServerSettingsService settings)
|
||||||
{
|
{
|
||||||
_config = config;
|
_settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetHttpClientFactory(IHttpClientFactory httpFactory)
|
public void SetHttpClientFactory(IHttpClientFactory httpFactory)
|
||||||
@@ -31,9 +30,9 @@ public class ToolService
|
|||||||
_httpFactory = httpFactory;
|
_httpFactory = httpFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ExecuteTimeoutSeconds => _config.GetValue<int>("ExecuteTimeout", 180);
|
private int ExecuteTimeoutSeconds => _settings.GetInt("ExecuteTimeout", 180);
|
||||||
public bool HasGitHubToken => !string.IsNullOrWhiteSpace(_config["GitHubToken"]);
|
public bool HasGitHubToken => !string.IsNullOrWhiteSpace(_settings.Get("GitHubToken"));
|
||||||
public bool HasSerperKey => !string.IsNullOrWhiteSpace(_config["SerperApiKey"]);
|
public bool HasSerperKey => !string.IsNullOrWhiteSpace(_settings.Get("SerperApiKey"));
|
||||||
public const string AskUserChoiceToolName = "ask_user_choice";
|
public const string AskUserChoiceToolName = "ask_user_choice";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -858,7 +857,7 @@ public class ToolService
|
|||||||
|
|
||||||
private async Task<string> ExecuteGitHubCodeSearch(JsonElement input, CancellationToken ct)
|
private async Task<string> ExecuteGitHubCodeSearch(JsonElement input, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var token = _config["GitHubToken"] ?? "";
|
var token = _settings.Get("GitHubToken");
|
||||||
if (string.IsNullOrWhiteSpace(token))
|
if (string.IsNullOrWhiteSpace(token))
|
||||||
return "Fehler: Kein GitHubToken konfiguriert. Bitte in den Server-Einstellungen hinterlegen.";
|
return "Fehler: Kein GitHubToken konfiguriert. Bitte in den Server-Einstellungen hinterlegen.";
|
||||||
|
|
||||||
@@ -961,7 +960,7 @@ public class ToolService
|
|||||||
|
|
||||||
private async Task<string> ExecuteSerperWebSearch(JsonElement input, CancellationToken ct)
|
private async Task<string> ExecuteSerperWebSearch(JsonElement input, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var apiKey = _config["SerperApiKey"] ?? "";
|
var apiKey = _settings.Get("SerperApiKey");
|
||||||
if (string.IsNullOrWhiteSpace(apiKey))
|
if (string.IsNullOrWhiteSpace(apiKey))
|
||||||
return "Fehler: Kein SerperApiKey konfiguriert. Bitte in den Server-Einstellungen hinterlegen.";
|
return "Fehler: Kein SerperApiKey konfiguriert. Bitte in den Server-Einstellungen hinterlegen.";
|
||||||
|
|
||||||
|
|||||||
+1
-14
@@ -6,18 +6,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Port": 19000,
|
"Port": 19000
|
||||||
"MaxIterations": 100,
|
|
||||||
"MaxHistory": 120,
|
|
||||||
"ReminderInterval": 30,
|
|
||||||
"ExecuteTimeout": 180,
|
|
||||||
"PayloadLogging": false,
|
|
||||||
"PayloadLoggingGzip": false,
|
|
||||||
"KreaToken": "",
|
|
||||||
"KreaApiKey": "",
|
|
||||||
"GeminiApiKey": "",
|
|
||||||
"GitHubToken": "",
|
|
||||||
"SerperApiKey": "",
|
|
||||||
"ScanIntervalHours": 0,
|
|
||||||
"ProxyUrl": ""
|
|
||||||
}
|
}
|
||||||
@@ -541,12 +541,10 @@
|
|||||||
</header>
|
</header>
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<p class="form-hint"><i class="fa fa-info-circle"></i> Änderungen werden in <code>appsettings.Production.json</code> gespeichert. Danach ist ein Neustart erforderlich.</p>
|
|
||||||
<div id="serverSettingsFields"></div>
|
<div id="serverSettingsFields"></div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button id="serverSettingsSaveBtn"><i class="fa fa-save"></i> Speichern</button>
|
<button id="serverSettingsSaveBtn"><i class="fa fa-save"></i> Speichern</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user