apiProvider->enabled; } public function enable(): bool { $this->apiProvider->enabled = true; return $this->apiProvider->save(); } public function disable(): bool { $this->apiProvider->enabled = false; return $this->apiProvider->save(); } public function getStatus(string $imageUUID): array { // Not supported directly; handled via getStyledImage polling. return ['status' => 'unknown']; } public function getProgress(string $imageUUID): array { return ['progress' => 0]; } public function processImageStyleChange(Image $image, Style $style): array { $this->assertApiConfig(); $modelId = $style->aiModel?->model_id; if (! $modelId || ! in_array($modelId, self::SUPPORTED_MODELS, true)) { throw new RuntimeException('Unsupported Leonardo model. Please choose a v2 model (Nano Banana Pro, FLUX.2 Pro, Seedream 4.0).'); } $referenceId = $this->uploadReferenceImage($image); $width = $style->parameters['width'] ?? 1024; $height = $style->parameters['height'] ?? 1024; $styleIds = $style->parameters['style_ids'] ?? []; $promptEnhance = $style->parameters['prompt_enhance'] ?? 'OFF'; $payload = [ 'model' => $modelId, 'parameters' => [ 'width' => $width, 'height' => $height, 'prompt' => $style->prompt, 'quantity' => 1, 'guidances' => [ 'image_reference' => [[ 'image' => [ 'id' => $referenceId, 'type' => 'UPLOADED', ], 'strength' => 'MID', ]], ], 'style_ids' => $styleIds, 'prompt_enhance' => $promptEnhance, ], 'public' => false, ]; $response = $this->http()->post(self::BASE_URL.'/generations', $payload); $data = $response->json(); $this->throwOnApiError($response, $data); $generationId = $data['generationId'] ?? $data['id'] ?? $data['data'][0]['generationId'] ?? $data['data'][0]['id'] ?? null; if (! $generationId) { $this->logError('Leonardo generation response missing generationId', ['response' => $data]); throw new RuntimeException('Leonardo generation did not return an id.'); } // Return identifier to allow polling return [ 'prompt_id' => $generationId, 'plugin' => 'LeonardoAi', ]; } public function getStyledImage(string $promptId): string { $this->assertApiConfig(); $response = $this->http()->get(self::BASE_URL.'/generations/'.$promptId); $data = $response->json(); $this->throwOnApiError($response, $data); $images = $data['images'] ?? $data['data'][0]['images'] ?? null; if (! $images || ! isset($images[0]['url'])) { throw new RuntimeException('Leonardo generation not ready yet or no image URL returned.'); } return $images[0]['url']; } public function testConnection(array $data): bool { $apiKey = $data['token'] ?? null; if (! $apiKey) { throw new RuntimeException('API key fehlt.'); } $payload = [ 'model' => self::SUPPORTED_MODELS[0], 'parameters' => [ 'prompt' => 'health check', 'width' => 512, 'height' => 512, 'quantity' => 1, ], 'public' => false, ]; $response = $this->http($apiKey)->post(self::BASE_URL.'/generations', $payload); $json = $response->json(); $this->throwOnApiError($response, $json); return true; } public function searchModels(string $searchTerm): array { $models = [ ['name' => 'Nano Banana Pro', 'id' => 'gemini-image-2'], ['name' => 'FLUX.2 Pro', 'id' => 'flux-pro-2.0'], ['name' => 'Seedream 4.0', 'id' => 'seedream-4.0'], ]; return array_values(array_filter($models, function ($model) use ($searchTerm) { return stripos($model['name'], $searchTerm) !== false || stripos($model['id'], $searchTerm) !== false; })); } public function checkAvailability(): array { try { $this->testConnection($this->apiProvider->toArray()); return [ 'available' => true, 'reason' => 'Connection successful', 'provider_id' => $this->apiProvider->id, 'provider_name' => $this->apiProvider->name, ]; } catch (\Exception $e) { return [ 'available' => false, 'reason' => $e->getMessage(), 'provider_id' => $this->apiProvider->id, 'provider_name' => $this->apiProvider->name, ]; } } private function uploadReferenceImage(Image $image): string { $path = public_path('storage/'.$image->path); if (! file_exists($path)) { throw new RuntimeException('Bild nicht gefunden: '.$path); } $response = $this->http()->attach('image', file_get_contents($path), basename($path)) ->post(self::BASE_URL.'/init-image', [ 'extension' => pathinfo($path, PATHINFO_EXTENSION), ]); $json = $response->json(); $this->throwOnApiError($response, $json); $id = $json['id'] ?? $json['imageId'] ?? $json['uploadId'] ?? $json['data'][0]['id'] ?? null; if (! $id) { $this->logError('Leonardo upload response missing id', ['response' => $json]); throw new RuntimeException('Upload to Leonardo failed (no id returned).'); } return $id; } private function http(?string $apiKey = null) { $key = $apiKey ?? $this->apiProvider->token; if (! $key) { throw new RuntimeException('Leonardo API key ist nicht gesetzt.'); } return Http::withHeaders([ 'Authorization' => 'Bearer '.$key, 'Accept' => 'application/json', ]); } private function throwOnApiError($response, ?array $data): void { if ($response->successful() && empty($data['errors'])) { return; } $error = $data['errors'][0] ?? null; if ($error) { $code = $error['code'] ?? 'error'; $message = $error['message'] ?? 'API error'; $task = $error['taskUUID'] ?? null; $detail = trim(($code ? $code.': ' : '').$message.' '.($task ? "(task {$task})" : '')); throw new RuntimeException($detail); } $status = $response->status(); $body = $response->body(); throw new RuntimeException('Leonardo API Fehler '.$status.($body ? ': '.Str::limit($body, 200) : '')); } private function assertApiConfig(): void { if (! $this->apiProvider->enabled) { throw new RuntimeException('API Provider ist deaktiviert.'); } if (! $this->apiProvider->token) { throw new RuntimeException('Leonardo API key fehlt.'); } } }