[ * 'token' => env('TAPTO_AI_TOKEN'), * 'base_url' => env('TAPTO_AI_BASE_URL', 'https://tapto.bg/api/ai'), * ], * * ПРИМЕР * $ai = app(\App\Services\TapToAIService::class); * $ai->getCredits(); // ['status' => true, 'credits' => 8.8] * $ai->studioGenerate(['type' => 'pets', 'scene' => 'living', 'subject' => 'dog'], * storage_path('app/bed.jpg')); // 2 снимки за 0,10 кр. */ class TapToAIService { protected string $apiToken; protected string $baseUrl; public function __construct() { $this->apiToken = (string) config('services.tapto_ai.token'); $this->baseUrl = rtrim(config('services.tapto_ai.base_url', 'https://tapto.bg/api/ai'), '/'); } protected function client(int $timeout = 120) { return Http::withToken($this->apiToken)->acceptJson()->timeout($timeout); } /** Наличните кредити. */ public function getCredits(): array { return $this->client()->get($this->baseUrl . '/credits')->json() ?? []; } /** Транзакциите: search, status, type, sort_by, sort_order, per_page, page. */ public function getTransactions(array $params = []): array { return $this->client()->get($this->baseUrl . '/transactions', $params)->json() ?? []; } /** Генерираните снимки (и от AI студиото): search, per_page, page. */ public function getHistory(array $params = []): array { return $this->client()->get($this->baseUrl . '/history', $params)->json() ?? []; } /** AI студио: видовете продукти, сцените, хората/животните, форматите и цената. */ public function studioPresets(string $locale = 'bg'): array { return $this->client()->get($this->baseUrl . '/studio/presets', ['locale' => $locale])->json() ?? []; } /** * 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(array $options, ?string $imagePath = null): array { $request = $this->client(240)->asMultipart(); if ($imagePath && is_file($imagePath)) { $request = $request->attach('input_image', file_get_contents($imagePath), basename($imagePath)); } return $request->post($this->baseUrl . '/studio/generate', array_filter($options, fn ($v) => $v !== null && $v !== ''))->json() ?? []; } /** * Блог статия (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(array $options): array { return $this->client()->post($this->baseUrl . '/blog-article', $options)->json() ?? []; } /** Блог статия: състоянието (pending|done|error) и статията, когато е готова. */ public function blogArticle(int $id): array { return $this->client()->get($this->baseUrl . '/blog-article/' . $id)->json() ?? []; } /** Блог статия: пуска и чака (до $maxSeconds), докато стане готова. */ public function writeBlogArticle(array $options, int $maxSeconds = 300): array { $start = $this->blogArticleStart($options); if (empty($start['id'])) { return $start; } $until = time() + $maxSeconds; do { sleep(5); $r = $this->blogArticle((int) $start['id']); } while (($r['state'] ?? '') === 'pending' && time() < $until); return $r; } /** Вашите блог статии: per_page, page. */ public function blogArticles(array $params = []): array { return $this->client()->get($this->baseUrl . '/blog-articles', $params)->json() ?? []; } /** Снимка по свободно описание (prompt) — 0,10 кр. */ public function generateImage(string $prompt, array $options = [], ?string $inputImagePath = null): array { $data = ['prompt' => $prompt] + $options; // model, width, height, steps, cfg_scale, negative_prompt, output_format if ($inputImagePath && is_file($inputImagePath)) { return $this->client(180)->attach('input_image', file_get_contents($inputImagePath), basename($inputImagePath)) ->post($this->baseUrl . '/generate', $data)->json() ?? []; } return $this->client(180)->post($this->baseUrl . '/generate', $data)->json() ?? []; } /** Премахване на фон — локален файл или адрес (0,01 кр.). */ public function removeBackground(string $imagePathOrUrl, ?string $model = null, string $outputFormat = 'PNG'): array { $data = array_filter(['model' => $model, 'output_format' => $outputFormat]); if (filter_var($imagePathOrUrl, FILTER_VALIDATE_URL)) { return $this->client()->post($this->baseUrl . '/remove-background', $data + ['image_url' => $imagePathOrUrl])->json() ?? []; } return $this->client()->attach('image', file_get_contents($imagePathOrUrl), basename($imagePathOrUrl)) ->post($this->baseUrl . '/remove-background', $data)->json() ?? []; } /** SEO Agent — SEO текстове за продукт на няколко езика (0,01 кр.). */ public function generateSeoAgent(string $siteName, string $siteUrl, string|array $languages, string $productName, float $productPrice, string $currency, string $description, array $imagePaths = [], ?string $additionalReqs = null, bool $includeIcons = false): array { $request = $this->client(180); foreach (array_values($imagePaths) as $i => $path) { if (is_file($path)) { $request = $request->attach("images[$i]", file_get_contents($path), basename($path)); } } return $request->post($this->baseUrl . '/seo-agent', array_filter([ '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, ], fn ($v) => $v !== null))->json() ?? []; } }