using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; using Gott.Models; namespace Gott.Services { public class KreaService { private readonly IHttpClientFactory _httpFactory; private readonly ServerSettingsService _settings; private readonly string _imagesDir; private string _token => _settings.Get("KreaToken"); private const string BASE_URL = "https://api.krea.ai"; public static readonly Dictionary IMAGE_MODELS = new Dictionary { { "flux", "/generate/image/bfl/flux-1-dev" }, { "flux-kontext", "/generate/image/bfl/flux-1-kontext-dev" }, { "flux-1.1-pro", "/generate/image/bfl/flux-1.1-pro" }, { "flux-1.1-pro-ultra", "/generate/image/bfl/flux-1.1-pro-ultra" }, { "nano-banana", "/generate/image/google/nano-banana" }, { "nano-banana-pro", "/generate/image/google/nano-banana-pro" }, { "imagen-3", "/generate/image/google/imagen-3" }, { "imagen-4", "/generate/image/google/imagen-4" }, { "imagen-4-fast", "/generate/image/google/imagen-4-fast" }, { "imagen-4-ultra", "/generate/image/google/imagen-4-ultra" }, { "ideogram-2-turbo", "/generate/image/ideogram/ideogram-2-turbo" }, { "ideogram-3", "/generate/image/ideogram/ideogram-3" }, { "seedream-3", "/generate/image/bytedance/seedream-3" }, { "seedream-4", "/generate/image/bytedance/seedream-4" }, { "chatgpt-image", "/generate/image/openai/gpt-image" }, { "runway-gen-4", "/generate/image/runway/gen-4" }, { "z-image", "/generate/image/z-image/z-image" }, { "qwen-2512", "/generate/image/qwen/2512" }, { "nano-banana-2", "/generate/image/google/nano-banana-2" }, { "seedream-5-lite", "/generate/image/bytedance/seedream-5-lite" }, { "seededit", "/generate/image/bytedance/seededit" }, }; // Models that don't support width/height/steps/guidance private static readonly HashSet MINIMAL_MODELS = new HashSet { "seedream-3", "runway-gen-4", "seedream-5-lite", "seededit" }; public bool IsConfigured => !string.IsNullOrWhiteSpace(_token); public KreaService(IHttpClientFactory httpFactory, ServerSettingsService settings, string dataDir) { _httpFactory = httpFactory; _settings = settings; _imagesDir = Path.Combine(dataDir, "images"); Directory.CreateDirectory(_imagesDir); } public async Task GenerateAsync( string prompt, string model = "flux", int width = 1024, int height = 1024, int steps = 25, double guidanceScale = 3.0, string seed = null, int batchSize = 1) { if (!IMAGE_MODELS.TryGetValue(model, out var endpoint)) throw new ArgumentException($"Unbekanntes Model: {model}. Verfügbar: {string.Join(", ", IMAGE_MODELS.Keys)}"); var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var record = new KreaImageRecord { Id = timestamp.ToString(), Prompt = prompt, Model = model, Width = width, Height = height, Steps = steps, GuidanceScale = guidanceScale, Seed = seed, BatchSize = Math.Max(1, Math.Min(4, batchSize)), Status = "pending", CreatedAt = DateTimeOffset.UtcNow.ToString("o"), }; // Build payload // Hinweis: Krea unterstützt 'batchSize' nicht mehr ("batch size is no longer supported, // submit one request per output") -> wir senden pro gewünschtem Bild eine eigene Anfrage. var payload = new Dictionary(); payload["prompt"] = prompt; if (!MINIMAL_MODELS.Contains(model)) { payload["width"] = width; payload["height"] = height; payload["steps"] = steps; payload["guidance_scale_flux"] = guidanceScale; } if (model == "seedream-3") payload["model"] = "seedream-3-0-t2i-250415"; if (!string.IsNullOrEmpty(seed)) payload["seed"] = seed; // Submit job(s) var client = _httpFactory.CreateClient(); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {_token}"); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (compatible; Gott/1.0)"); var json = JsonSerializer.Serialize(payload); // Eine Anfrage pro gewünschtem Bild (batchSize wird serverseitig nicht mehr akzeptiert). var jobs = new List(); for (int b = 0; b < record.BatchSize; b++) { var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync($"{BASE_URL}{endpoint}", content); var responseBody = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) throw new Exception($"Krea API Fehler {(int)response.StatusCode}: {responseBody}"); var node = JsonNode.Parse(responseBody); if (node is JsonArray arr) jobs.AddRange(arr.Where(j => j != null)); else if (node != null) jobs.Add(node); } // Wait for all jobs var allUrls = new List(); var jobResults = new List(); foreach (var job in jobs) { var jobId = job["job_id"]?.GetValue() ?? throw new Exception("Kein job_id in Response"); var result = await WaitForJobAsync(client, jobId); var urls = result["result"]?["urls"]?.AsArray(); if (urls != null) foreach (var u in urls) allUrls.Add(u.GetValue()); jobResults.Add(result); } // Download images var savedFiles = await SaveImagesAsync(client, allUrls, timestamp); // Save metadata var metaFile = await SaveMetadataAsync(timestamp, record, allUrls, savedFiles, jobResults); record.Status = "completed"; record.Files = savedFiles; record.Urls = allUrls; record.MetaFile = metaFile; record.CompletedAt = DateTimeOffset.UtcNow.ToString("o"); return record; } private async Task WaitForJobAsync(HttpClient client, string jobId, int timeoutSeconds = 120) { var deadline = DateTime.UtcNow.AddSeconds(timeoutSeconds); while (DateTime.UtcNow < deadline) { var resp = await client.GetAsync($"{BASE_URL}/jobs/{jobId}"); var body = await resp.Content.ReadAsStringAsync(); var job = JsonNode.Parse(body); var status = job["status"]?.GetValue(); if (status == "completed") return job; if (status == "failed") throw new Exception($"Job fehlgeschlagen: {body}"); if (status == "cancelled") throw new Exception("Job wurde abgebrochen"); await Task.Delay(2000); } throw new TimeoutException($"Job {jobId} hat nicht innerhalb von {timeoutSeconds}s geantwortet"); } private async Task> SaveImagesAsync(HttpClient client, List urls, long timestamp) { var saved = new List(); for (int i = 0; i < urls.Count; i++) { var url = urls[i]; try { var resp = await client.GetAsync(url); var bytes = await resp.Content.ReadAsByteArrayAsync(); // Determine extension var ext = ".webp"; var contentType = resp.Content.Headers.ContentType?.MediaType ?? ""; var extMap = new Dictionary { { "image/png", ".png" }, { "image/jpeg", ".jpg" }, { "image/webp", ".webp" }, { "image/gif", ".gif" }, }; if (extMap.TryGetValue(contentType, out var mappedExt)) ext = mappedExt; var filename = $"{timestamp}-{i + 1}{ext}"; var filepath = Path.Combine(_imagesDir, filename); await File.WriteAllBytesAsync(filepath, bytes); saved.Add(filename); // Nur Dateiname, kein Pfad } catch (Exception ex) { Console.WriteLine($"[KreaService] Fehler beim Download von {url}: {ex.Message}"); } } return saved; } private async Task SaveMetadataAsync( long timestamp, KreaImageRecord record, List urls, List savedFiles, List jobResults) { var meta = new { timestamp, timestamp_iso = DateTimeOffset.FromUnixTimeSeconds(timestamp).ToString("o"), input = new { prompt = record.Prompt, model = record.Model, width = record.Width, height = record.Height, steps = record.Steps, guidance_scale = record.GuidanceScale, seed = record.Seed, batch_size = record.BatchSize, }, output = new { urls, files = savedFiles, image_count = urls.Count, }, jobs = jobResults.Select(j => j.ToJsonString()).ToList(), }; var metaPath = Path.Combine(_imagesDir, $"{timestamp}.json"); var json = JsonSerializer.Serialize(meta, new JsonSerializerOptions { WriteIndented = true }); await File.WriteAllTextAsync(metaPath, json); return $"{timestamp}.json"; } public List ListImages() { var result = new List(); if (!Directory.Exists(_imagesDir)) return result; foreach (var metaFile in Directory.GetFiles(_imagesDir, "*.json").OrderByDescending(f => f)) { try { var json = File.ReadAllText(metaFile); var node = JsonNode.Parse(json); var timestamp = node["timestamp"]?.GetValue() ?? 0; var input = node["input"]; var output = node["output"]; var files = output?["files"]?.AsArray() .Select(f => f.GetValue()).ToList() ?? new List(); result.Add(new KreaImageRecord { Id = timestamp.ToString(), Prompt = input?["prompt"]?.GetValue(), Model = input?["model"]?.GetValue(), Width = input?["width"]?.GetValue() ?? 1024, Height = input?["height"]?.GetValue() ?? 1024, Steps = input?["steps"]?.GetValue() ?? 25, GuidanceScale = input?["guidance_scale"]?.GetValue() ?? 3.0, Seed = input?["seed"]?.GetValue(), BatchSize = input?["batch_size"]?.GetValue() ?? 1, Status = "completed", Files = files, Urls = output?["urls"]?.AsArray() .Select(u => u.GetValue()).ToList() ?? new List(), CreatedAt = node["timestamp_iso"]?.GetValue(), MetaFile = Path.GetFileName(metaFile), }); } catch { /* ungültige Meta-Datei überspringen */ } } return result; } public string GetImagePath(string filename) { var path = Path.Combine(_imagesDir, filename); return File.Exists(path) ? path : null; } } }