getCredits()); * * // AI студио — 2 снимки на легло за куче в хол, с куче (0,10 кр.) * $r = $ai->studioGenerate(['type' => 'pets', 'scene' => 'living', 'subject' => 'dog'], __DIR__ . '/bed.jpg'); * foreach ($r['images'] ?? [] as $img) echo $img['url'], PHP_EOL; */ class TapToAIService { private $apiToken; private $baseUrl; public function __construct($apiToken, $baseUrl = 'https://tapto.bg/api/ai') { $this->apiToken = $apiToken; $this->baseUrl = rtrim($baseUrl, '/'); } /** * @param string $endpoint напр. 'credits' * @param string $method 'GET' или 'POST' * @param array $data тяло (POST) или параметри (GET) * @param array $files ['поле' => 'път/до/файл'] */ private function sendRequest($endpoint, $method = 'POST', $data = [], $files = [], $timeout = 120) { $url = $this->baseUrl . '/' . ltrim($endpoint, '/'); $headers = ['Authorization: Bearer ' . $this->apiToken, 'Accept: application/json']; $ch = curl_init(); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); $data = array_filter($data, function ($v) { return $v !== null && $v !== ''; }); if ($files) { foreach ($files as $key => $path) { if (is_file($path)) { $data[$key] = new CURLFile($path); } } curl_setopt($ch, CURLOPT_POSTFIELDS, $data); } else { $headers[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } } elseif ($data) { $url .= '?' . http_build_query($data); } curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); $response = curl_exec($ch); curl_close($ch); if ($response === false) { return ['status' => false, 'messages' => 'Няма връзка със сървъра']; } return json_decode($response, true) ?: ['status' => false, 'messages' => 'Невалиден JSON отговор', 'raw' => $response]; } /** Наличните кредити. */ public function getCredits() { return $this->sendRequest('credits', 'GET'); } /** Транзакциите: search, status, type, sort_by, sort_order, per_page, page. */ public function getTransactions($params = []) { return $this->sendRequest('transactions', 'GET', $params); } /** Генерираните снимки: search, per_page, page. */ public function getHistory($params = []) { return $this->sendRequest('history', 'GET', $params); } /** AI студио: видове продукти, сцени, хора/животни, формати и цена. */ public function studioPresets($locale = 'bg') { return $this->sendRequest('studio/presets', 'GET', ['locale' => $locale]); } /** * AI студио: 2 продуктови снимки наведнъж (0,10 кр.). * * @param array $options type, scene, subject, format, color, lighting, mood, angle, season, * fidelity, product, extra, avoid, seed, steps, cfg * @param string|null $imagePath снимка на продукта; без нея е задължително 'product' */ public function studioGenerate($options, $imagePath = null) { $files = $imagePath ? ['input_image' => $imagePath] : []; return $this->sendRequest('studio/generate', 'POST', $options, $files, 240); } /** * Блог статия (0,20 кр. + 0,10 кр. корица): пуска писането и връща ['id' => …]. Опции: topic (задължително), * keywords, language, length (short|medium|long), tone (friendly|expert|selling|simple), audience, notes, * cover (true/false), cover_style (photo|illustration|3d|minimal). */ public function blogArticleStart($options) { return $this->sendRequest('blog-article', 'POST', $options); } /** Блог статия: състоянието (pending|done|error) и статията, когато е готова. */ public function blogArticle($id) { return $this->sendRequest('blog-article/' . (int) $id, 'GET'); } /** Блог статия: пуска и чака (до $maxSeconds), докато стане готова. */ public function writeBlogArticle($options, $maxSeconds = 300) { $start = $this->blogArticleStart($options); if (empty($start['id'])) { return $start; } $until = time() + $maxSeconds; do { sleep(5); $r = $this->blogArticle($start['id']); } while (($r['state'] ?? '') === 'pending' && time() < $until); return $r; } /** Вашите блог статии: per_page, page. */ public function blogArticles($params = []) { return $this->sendRequest('blog-articles', 'GET', $params); } /** Снимка по свободно описание (prompt) — 0,10 кр. Опции: model, width, height, steps, cfg_scale, negative_prompt, output_format. */ public function generateImage($prompt, $options = [], $inputImagePath = null) { $files = $inputImagePath ? ['input_image' => $inputImagePath] : []; return $this->sendRequest('generate', 'POST', ['prompt' => $prompt] + $options, $files, 180); } /** Премахване на фон — локален файл или адрес (0,01 кр.). */ public function removeBackground($imagePathOrUrl, $model = null, $outputFormat = 'PNG') { $data = ['model' => $model, 'output_format' => $outputFormat]; if (filter_var($imagePathOrUrl, FILTER_VALIDATE_URL)) { return $this->sendRequest('remove-background', 'POST', $data + ['image_url' => $imagePathOrUrl]); } return $this->sendRequest('remove-background', 'POST', $data, ['image' => $imagePathOrUrl]); } /** SEO Agent — SEO текстове за продукт на няколко езика (0,01 кр.). */ public function generateSeoAgent($siteName, $siteUrl, $languages, $productName, $productPrice, $currency, $description, $imagePaths = [], $additionalReqs = null, $includeIcons = false) { $data = [ 'site_name' => $siteName, 'site_url' => $siteUrl, 'languages' => is_array($languages) ? implode(', ', $languages) : $languages, 'product_name' => $productName, 'product_price' => $productPrice, 'currency' => $currency, 'description' => $description, 'additional_requirements' => $additionalReqs, 'include_icons' => $includeIcons ? 1 : 0, ]; $files = []; foreach (array_values($imagePaths) as $i => $path) { $files["images[$i]"] = $path; } return $this->sendRequest('seo-agent', 'POST', $data, $files, 180); } }