/* * TapToAIService — C# клас за TapTo AI API (.NET 6+, HttpClient). * Документация: https://tapto.bg/app/ai-api-integration * * ПРИМЕР * var ai = new TapToAIService("ВАШИЯТ_API_ТОКЕН"); * Console.WriteLine(await ai.GetCreditsAsync()); * * // AI студио — 2 снимки на легло за куче в хол, с куче (0,10 кр.) * var json = await ai.StudioGenerateAsync(new Dictionary { * ["type"] = "pets", ["scene"] = "living", ["subject"] = "dog" * }, "bed.jpg"); */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading.Tasks; public class TapToAIService { private readonly string _baseUrl; private readonly HttpClient _http; public TapToAIService(string apiToken, string baseUrl = "https://tapto.bg/api/ai") { _baseUrl = baseUrl.TrimEnd('/'); _http = new HttpClient { Timeout = TimeSpan.FromSeconds(240) }; _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiToken); _http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); } private async Task GetAsync(string endpoint, IDictionary query = null) { var qs = query == null || query.Count == 0 ? "" : "?" + string.Join("&", query.Select(kv => Uri.EscapeDataString(kv.Key) + "=" + Uri.EscapeDataString(kv.Value))); var response = await _http.GetAsync(_baseUrl + "/" + endpoint + qs); return await response.Content.ReadAsStringAsync(); } private async Task PostJsonAsync(string endpoint, object body) { var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); var response = await _http.PostAsync(_baseUrl + "/" + endpoint, content); return await response.Content.ReadAsStringAsync(); } private async Task PostMultipartAsync(string endpoint, IDictionary fields, IDictionary files) { using var form = new MultipartFormDataContent(); foreach (var kv in fields.Where(kv => !string.IsNullOrEmpty(kv.Value))) form.Add(new StringContent(kv.Value), kv.Key); foreach (var kv in files.Where(kv => File.Exists(kv.Value))) form.Add(new ByteArrayContent(await File.ReadAllBytesAsync(kv.Value)), kv.Key, Path.GetFileName(kv.Value)); var response = await _http.PostAsync(_baseUrl + "/" + endpoint, form); return await response.Content.ReadAsStringAsync(); } /// Наличните кредити. public Task GetCreditsAsync() => GetAsync("credits"); /// Транзакциите: search, status, type, sort_by, sort_order, per_page, page. public Task GetTransactionsAsync(IDictionary query = null) => GetAsync("transactions", query); /// Генерираните снимки: search, per_page, page. public Task GetHistoryAsync(IDictionary query = null) => GetAsync("history", query); /// AI студио: видове продукти, сцени, хора/животни, формати и цена. public Task StudioPresetsAsync(string locale = "bg") => GetAsync("studio/presets", new Dictionary { ["locale"] = locale }); /// /// AI студио: 2 продуктови снимки наведнъж (0,10 кр.). /// options: type, scene, subject, format, color, lighting, mood, angle, season, fidelity, product, extra, avoid, seed, steps, cfg. /// Без imagePath е задължително "product". /// public Task StudioGenerateAsync(IDictionary options, string imagePath = null) { var files = new Dictionary(); if (!string.IsNullOrEmpty(imagePath)) files["input_image"] = imagePath; return PostMultipartAsync("studio/generate", options, files); } /// /// Блог статия (0,20 кр. + 0,10 кр. корица): пуска писането, връща JSON с "id". /// options: topic (задължително), keywords, language, length, tone, audience, notes, cover ("1"/"0"), cover_style. /// public Task BlogArticleStartAsync(IDictionary options) => PostMultipartAsync("blog-article", options, new Dictionary()); /// Блог статия: състоянието (pending|done|error) и статията, когато е готова. public Task BlogArticleAsync(int id) => GetAsync("blog-article/" + id); /// Блог статия: пуска и чака (до maxSeconds), докато стане готова. public async Task WriteBlogArticleAsync(IDictionary options, int maxSeconds = 300) { var start = JsonDocument.Parse(await BlogArticleStartAsync(options)).RootElement; if (!start.TryGetProperty("id", out var idProp)) return start.GetRawText(); var until = DateTime.UtcNow.AddSeconds(maxSeconds); string json; do { await Task.Delay(5000); json = await BlogArticleAsync(idProp.GetInt32()); } while (JsonDocument.Parse(json).RootElement.TryGetProperty("state", out var st) && st.GetString() == "pending" && DateTime.UtcNow < until); return json; } /// Вашите блог статии. public Task BlogArticlesAsync(IDictionary query = null) => GetAsync("blog-articles", query); /// Снимка по свободно описание (prompt) — 0,10 кр. Опции: model, width, height, steps, cfg_scale, negative_prompt, output_format. public Task GenerateImageAsync(string prompt, IDictionary options = null, string inputImagePath = null) { var fields = new Dictionary(options ?? new Dictionary()) { ["prompt"] = prompt }; var files = new Dictionary(); if (!string.IsNullOrEmpty(inputImagePath)) files["input_image"] = inputImagePath; return PostMultipartAsync("generate", fields, files); } /// Премахване на фон — локален файл или адрес (0,01 кр.). public Task RemoveBackgroundAsync(string imagePathOrUrl, string model = null, string outputFormat = "PNG") { var fields = new Dictionary { ["output_format"] = outputFormat }; if (!string.IsNullOrEmpty(model)) fields["model"] = model; if (imagePathOrUrl.StartsWith("http://") || imagePathOrUrl.StartsWith("https://")) { fields["image_url"] = imagePathOrUrl; return PostMultipartAsync("remove-background", fields, new Dictionary()); } return PostMultipartAsync("remove-background", fields, new Dictionary { ["image"] = imagePathOrUrl }); } /// SEO Agent — SEO текстове за продукт на няколко езика (0,01 кр.). public Task GenerateSeoAgentAsync(string siteName, string siteUrl, string languages, string productName, double productPrice, string currency, string description, string[] imagePaths = null, string additionalRequirements = null, bool includeIcons = false) { var fields = new Dictionary { ["site_name"] = siteName, ["site_url"] = siteUrl, ["languages"] = languages, ["product_name"] = productName, ["product_price"] = productPrice.ToString(System.Globalization.CultureInfo.InvariantCulture), ["currency"] = currency, ["description"] = description, ["additional_requirements"] = additionalRequirements, ["include_icons"] = includeIcons ? "1" : "0", }; var files = new Dictionary(); for (int i = 0; i < (imagePaths?.Length ?? 0); i++) files["images[" + i + "]"] = imagePaths[i]; return PostMultipartAsync("seo-agent", fields, files); } }