feat: Krea Image Generation Endpoint
- KreaService: GenerateAsync() mit Job-Polling, Image-Download, Metadaten-Speicherung
- KreaEndpoint: POST /api/images/generate, GET /api/images, GET /api/images/file/{filename}, GET /api/images/status
- KreaImageRecord: Output-Model
- Output: data/images/<timestamp>-<index>.<ext> + data/images/<timestamp>.json
- 16 Modelle: flux, flux-kontext, flux-1.1-pro, imagen-3/4, chatgpt-image, runway-gen-4, etc.
- appsettings.json: KreaToken (Format: KEY_ID:SECRET), obsolete TelegramBotToken/WebhookSecret entfernt
This commit is contained in:
1 parent
f552b44b05
commit
26da25967d
5 files changed
+443
-1
No files matched your search
@@ -0,0 +1,112 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Gott.Services;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
|
namespace Gott.Endpoints
|
||||||
|
{
|
||||||
|
public static class KreaEndpoint
|
||||||
|
{
|
||||||
|
public static void MapKreaEndpoints(this IEndpointRouteBuilder app)
|
||||||
|
{
|
||||||
|
app.MapPost("/api/images/generate", HandleGenerate);
|
||||||
|
app.MapGet("/api/images", HandleList);
|
||||||
|
app.MapGet("/api/images/file/{filename}", HandleFile);
|
||||||
|
app.MapGet("/api/images/status", HandleStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleGenerate(HttpRequest req, HttpResponse res, KreaService krea)
|
||||||
|
{
|
||||||
|
if (!krea.IsConfigured)
|
||||||
|
{
|
||||||
|
res.StatusCode = 400;
|
||||||
|
await res.WriteAsJsonAsync(new { error = "Krea API nicht konfiguriert (KreaToken fehlt)" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonElement body;
|
||||||
|
try { body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body); }
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
res.StatusCode = 400;
|
||||||
|
await res.WriteAsJsonAsync(new { error = "Ungültiger JSON-Body" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prompt = body.TryGetProperty("prompt", out var p) ? p.GetString() : null;
|
||||||
|
if (string.IsNullOrWhiteSpace(prompt))
|
||||||
|
{
|
||||||
|
res.StatusCode = 400;
|
||||||
|
await res.WriteAsJsonAsync(new { error = "prompt ist erforderlich" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var model = body.TryGetProperty("model", out var m) ? m.GetString() : "flux";
|
||||||
|
var width = body.TryGetProperty("width", out var w) ? w.GetInt32() : 1024;
|
||||||
|
var height = body.TryGetProperty("height", out var h) ? h.GetInt32() : 1024;
|
||||||
|
var steps = body.TryGetProperty("steps", out var st) ? st.GetInt32() : 25;
|
||||||
|
var guidanceScale = body.TryGetProperty("guidanceScale", out var g) ? g.GetDouble() : 3.0;
|
||||||
|
var seed = body.TryGetProperty("seed", out var sd) ? sd.GetString() : null;
|
||||||
|
var batchSize = body.TryGetProperty("batchSize", out var b) ? b.GetInt32() : 1;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await krea.GenerateAsync(prompt, model, width, height, steps, guidanceScale, seed, batchSize);
|
||||||
|
await res.WriteAsJsonAsync(result);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
res.StatusCode = 500;
|
||||||
|
await res.WriteAsJsonAsync(new { error = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleList(HttpRequest req, HttpResponse res, KreaService krea)
|
||||||
|
{
|
||||||
|
var images = krea.ListImages();
|
||||||
|
await res.WriteAsJsonAsync(images);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleFile(HttpRequest req, HttpResponse res, KreaService krea, string filename)
|
||||||
|
{
|
||||||
|
// Sicherheitscheck – kein Path Traversal
|
||||||
|
if (filename.Contains("..") || filename.Contains("/") || filename.Contains("\\"))
|
||||||
|
{
|
||||||
|
res.StatusCode = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = krea.GetImagePath(filename);
|
||||||
|
if (path == null)
|
||||||
|
{
|
||||||
|
res.StatusCode = 404;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ext = Path.GetExtension(filename).ToLower();
|
||||||
|
var contentType = ext switch
|
||||||
|
{
|
||||||
|
".png" => "image/png",
|
||||||
|
".jpg" => "image/jpeg",
|
||||||
|
".jpeg" => "image/jpeg",
|
||||||
|
".gif" => "image/gif",
|
||||||
|
_ => "image/webp",
|
||||||
|
};
|
||||||
|
|
||||||
|
res.ContentType = contentType;
|
||||||
|
await res.SendFileAsync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task HandleStatus(HttpRequest req, HttpResponse res, KreaService krea)
|
||||||
|
{
|
||||||
|
await res.WriteAsJsonAsync(new
|
||||||
|
{
|
||||||
|
configured = krea.IsConfigured,
|
||||||
|
models = KreaService.IMAGE_MODELS.Keys,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Gott.Models
|
||||||
|
{
|
||||||
|
public class KreaImageRecord
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Prompt { get; set; }
|
||||||
|
public string Model { get; set; }
|
||||||
|
public int Width { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
public int Steps { get; set; }
|
||||||
|
public double GuidanceScale { get; set; }
|
||||||
|
public string Seed { get; set; }
|
||||||
|
public int BatchSize { get; set; }
|
||||||
|
public string Status { get; set; } // pending, completed, failed
|
||||||
|
public List<string> Files { get; set; } = new List<string>();
|
||||||
|
public List<string> Urls { get; set; } = new List<string>();
|
||||||
|
public string Error { get; set; }
|
||||||
|
public string CreatedAt { get; set; }
|
||||||
|
public string CompletedAt { get; set; }
|
||||||
|
public string MetaFile { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,11 @@ builder.Services.AddSingleton<TelegramService>(sp =>
|
|||||||
sp.GetRequiredService<AnthropicService>(),
|
sp.GetRequiredService<AnthropicService>(),
|
||||||
maxIterations));
|
maxIterations));
|
||||||
|
|
||||||
|
// Krea Image Generation
|
||||||
|
var kreaToken = builder.Configuration["KreaToken"] ?? "";
|
||||||
|
builder.Services.AddSingleton(sp =>
|
||||||
|
new KreaService(sp.GetRequiredService<IHttpClientFactory>(), kreaToken, dataDir));
|
||||||
|
|
||||||
// Cronjob-Service (BackgroundService)
|
// Cronjob-Service (BackgroundService)
|
||||||
builder.Services.AddSingleton<CronjobService>();
|
builder.Services.AddSingleton<CronjobService>();
|
||||||
builder.Services.AddHostedService<CronjobService>(sp => sp.GetRequiredService<CronjobService>());
|
builder.Services.AddHostedService<CronjobService>(sp => sp.GetRequiredService<CronjobService>());
|
||||||
@@ -74,5 +79,6 @@ app.MapProfileEndpoints();
|
|||||||
app.MapSettingsEndpoints();
|
app.MapSettingsEndpoints();
|
||||||
app.MapTelegramEndpoints();
|
app.MapTelegramEndpoints();
|
||||||
app.MapCronjobEndpoints();
|
app.MapCronjobEndpoints();
|
||||||
|
app.MapKreaEndpoints();
|
||||||
|
|
||||||
app.Run("http://0.0.0.0:19000");
|
app.Run("http://0.0.0.0:19000");
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
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 string _token;
|
||||||
|
private readonly string _imagesDir;
|
||||||
|
|
||||||
|
private const string BASE_URL = "https://api.krea.ai";
|
||||||
|
|
||||||
|
public static readonly Dictionary<string, string> IMAGE_MODELS = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "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" },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Models that don't support width/height/steps/guidance
|
||||||
|
private static readonly HashSet<string> MINIMAL_MODELS = new HashSet<string>
|
||||||
|
{
|
||||||
|
"seedream-3", "runway-gen-4"
|
||||||
|
};
|
||||||
|
|
||||||
|
public bool IsConfigured => !string.IsNullOrWhiteSpace(_token);
|
||||||
|
|
||||||
|
public KreaService(IHttpClientFactory httpFactory, string token, string dataDir)
|
||||||
|
{
|
||||||
|
_httpFactory = httpFactory;
|
||||||
|
_token = token;
|
||||||
|
_imagesDir = Path.Combine(dataDir, "images");
|
||||||
|
Directory.CreateDirectory(_imagesDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<KreaImageRecord> 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
|
||||||
|
var payload = new Dictionary<string, object>();
|
||||||
|
payload["prompt"] = prompt;
|
||||||
|
payload["batchSize"] = record.BatchSize;
|
||||||
|
|
||||||
|
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);
|
||||||
|
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 jobsNode = JsonNode.Parse(responseBody);
|
||||||
|
var jobs = jobsNode is JsonArray arr ? arr.ToList() : new List<JsonNode> { jobsNode };
|
||||||
|
|
||||||
|
// Wait for all jobs
|
||||||
|
var allUrls = new List<string>();
|
||||||
|
var jobResults = new List<JsonNode>();
|
||||||
|
|
||||||
|
foreach (var job in jobs)
|
||||||
|
{
|
||||||
|
var jobId = job["job_id"]?.GetValue<string>()
|
||||||
|
?? 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<string>());
|
||||||
|
|
||||||
|
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<JsonNode> 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<string>();
|
||||||
|
|
||||||
|
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<List<string>> SaveImagesAsync(HttpClient client, List<string> urls, long timestamp)
|
||||||
|
{
|
||||||
|
var saved = new List<string>();
|
||||||
|
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<string, string>
|
||||||
|
{
|
||||||
|
{ "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<string> SaveMetadataAsync(
|
||||||
|
long timestamp,
|
||||||
|
KreaImageRecord record,
|
||||||
|
List<string> urls,
|
||||||
|
List<string> savedFiles,
|
||||||
|
List<JsonNode> 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<KreaImageRecord> ListImages()
|
||||||
|
{
|
||||||
|
var result = new List<KreaImageRecord>();
|
||||||
|
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<long>() ?? 0;
|
||||||
|
var input = node["input"];
|
||||||
|
var output = node["output"];
|
||||||
|
|
||||||
|
var files = output?["files"]?.AsArray()
|
||||||
|
.Select(f => f.GetValue<string>()).ToList() ?? new List<string>();
|
||||||
|
|
||||||
|
result.Add(new KreaImageRecord
|
||||||
|
{
|
||||||
|
Id = timestamp.ToString(),
|
||||||
|
Prompt = input?["prompt"]?.GetValue<string>(),
|
||||||
|
Model = input?["model"]?.GetValue<string>(),
|
||||||
|
Width = input?["width"]?.GetValue<int>() ?? 1024,
|
||||||
|
Height = input?["height"]?.GetValue<int>() ?? 1024,
|
||||||
|
Steps = input?["steps"]?.GetValue<int>() ?? 25,
|
||||||
|
GuidanceScale = input?["guidance_scale"]?.GetValue<double>() ?? 3.0,
|
||||||
|
Seed = input?["seed"]?.GetValue<string>(),
|
||||||
|
BatchSize = input?["batch_size"]?.GetValue<int>() ?? 1,
|
||||||
|
Status = "completed",
|
||||||
|
Files = files,
|
||||||
|
Urls = output?["urls"]?.AsArray()
|
||||||
|
.Select(u => u.GetValue<string>()).ToList() ?? new List<string>(),
|
||||||
|
CreatedAt = node["timestamp_iso"]?.GetValue<string>(),
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -9,5 +9,6 @@
|
|||||||
"AnthropicToken": "",
|
"AnthropicToken": "",
|
||||||
"MaxIterations": 100,
|
"MaxIterations": 100,
|
||||||
"PayloadLogging": false,
|
"PayloadLogging": false,
|
||||||
"PayloadLoggingGzip": false
|
"PayloadLoggingGzip": false,
|
||||||
|
"KreaToken": ""
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user