apiProvider = $apiProvider; $this->logInfo('RunwareAi plugin initialized.', ['provider_name' => $apiProvider->name]); } public function getIdentifier(): string { return 'runwareai'; } public function getName(): string { return 'RunwareAI'; } public function isEnabled(): bool { return $this->apiProvider->enabled; } public function enable(): bool { $this->apiProvider->enabled = true; $result = $this->apiProvider->save(); if ($result) { $this->logInfo('RunwareAi plugin enabled.', ['provider_name' => $this->apiProvider->name]); } else { $this->logError('Failed to enable RunwareAi plugin.', ['provider_name' => $this->apiProvider->name]); } return $result; } public function disable(): bool { $this->apiProvider->enabled = false; $result = $this->apiProvider->save(); if ($result) { $this->logInfo('RunwareAi plugin disabled.', ['provider_name' => $this->apiProvider->name]); } else { $this->logError('Failed to disable RunwareAi plugin.', ['provider_name' => $this->apiProvider->name]); } return $result; } public function getStyledImage(string $promptId): string { throw new \Exception('RunwareAi does not support fetching styled images by prompt ID.'); } public function getStatus(string $imageUUID): array { $this->logDebug('Getting status for image.', ['image_uuid' => $imageUUID]); // Implement RunwareAI specific status check return ['status' => 'unknown']; } public function getProgress(string $imageUUID): array { $this->logDebug('Getting progress for image.', ['image_uuid' => $imageUUID]); // Implement RunwareAI specific progress check return ['progress' => 0]; } public function processImageStyleChange(\App\Models\Image $image, \App\Models\Style $style): array { // Step 1: Upload the original image $uploadResult = $this->upload(public_path('storage/'.$image->path)); if (! isset($uploadResult['data'][0]['imageUUID'])) { throw new \Exception('Image upload to AI service failed or returned no UUID.'); } $seedImageUUID = $uploadResult['data'][0]['imageUUID']; // Step 2: Request style change using the uploaded image's UUID $result = $this->styleChangeRequest($style, $seedImageUUID); if (! isset($result['base64Data'])) { throw new \Exception('AI service did not return base64 image data.'); } return $result; } public function testConnection(array $data): bool { $apiUrl = rtrim($data['api_url'], '/'); $token = $data['token'] ?? null; if (! $apiUrl || ! $token) { $this->logError('RunwareAI connection test failed: API URL or Token missing.'); throw new \RuntimeException('API URL oder Token fehlt.'); } try { $payload = [[ 'taskType' => 'authentication', 'apiKey' => $token, 'taskUUID' => (string) Str::uuid(), ]]; $response = Http::withHeaders([ 'Content-Type' => 'application/json', 'Accept' => 'application/json', ])->timeout(5)->post($apiUrl, $payload); $responseData = $response->json(); // Official Runware format: errors array with code/message. if (isset($responseData['errors'][0])) { $error = $responseData['errors'][0]; $messageParts = [ $error['code'] ?? 'error', $error['message'] ?? null, ]; $detail = implode(': ', array_filter($messageParts)); $this->logError('RunwareAI connection test failed: API returned error.', [ 'status' => $response->status(), 'error' => $error, ]); throw new \RuntimeException($detail ?: 'Runware API Fehler.'); } if ($response->failed()) { $this->logError('RunwareAI connection test failed: HTTP error.', [ 'status' => $response->status(), 'body' => $response->body(), ]); throw new \RuntimeException('HTTP '.$response->status().' beim Test der Runware-API.'); } $this->logInfo('RunwareAI connection test successful: Authentication successful.', [ 'status' => $response->status(), 'response' => $responseData, ]); return true; } catch (\Exception $e) { $this->logError('RunwareAI connection test failed: Exception caught.', ['error' => $e->getMessage()]); throw $e; } } public function checkAvailability(): array { $this->logInfo('Checking RunwareAI availability.'); if (! $this->apiProvider->enabled) { $this->logDebug('RunwareAI provider is disabled.'); return [ 'available' => false, 'reason' => 'Provider is disabled', 'provider_id' => $this->apiProvider->id, 'provider_name' => $this->apiProvider->name, ]; } if (empty($this->apiProvider->api_url)) { $this->logDebug('RunwareAI API URL is not configured.'); return [ 'available' => false, 'reason' => 'API URL not configured', 'provider_id' => $this->apiProvider->id, 'provider_name' => $this->apiProvider->name, ]; } if (empty($this->apiProvider->token)) { $this->logDebug('RunwareAI API token is not configured.'); return [ 'available' => false, 'reason' => 'API token not configured', 'provider_id' => $this->apiProvider->id, 'provider_name' => $this->apiProvider->name, ]; } try { $response = Http::withHeaders([ 'Content-Type' => 'application/json', 'Accept' => 'application/json', ])->timeout(5)->post(rtrim($this->apiProvider->api_url, '/'), [ 'taskType' => 'authentication', 'apiKey' => $this->apiProvider->token, 'taskUUID' => (string) Str::uuid(), ]); $responseData = $response->json(); if ($response->successful() && isset($responseData['data']) && ! isset($responseData['error'])) { $this->logInfo('RunwareAI is available.'); return [ 'available' => true, 'reason' => 'Connection successful', 'provider_id' => $this->apiProvider->id, 'provider_name' => $this->apiProvider->name, ]; } else { $errorMessage = $responseData['error'] ?? 'Unknown error'; $this->logError('RunwareAI connection failed.', [ 'status' => $response->status(), 'error_message' => $errorMessage, ]); return [ 'available' => false, 'reason' => 'Connection failed: '.$errorMessage, 'provider_id' => $this->apiProvider->id, 'provider_name' => $this->apiProvider->name, ]; } } catch (\Exception $e) { $this->logError('RunwareAI availability check failed.', ['error' => $e->getMessage()]); return [ 'available' => false, 'reason' => 'Connection error: '.$e->getMessage(), 'provider_id' => $this->apiProvider->id, 'provider_name' => $this->apiProvider->name, ]; } } public function searchModels(string $searchTerm): array { $this->logInfo('Attempting model search on RunwareAI.', ['searchTerm' => $searchTerm]); if (! $this->apiProvider->api_url || ! $this->apiProvider->token) { $this->logError('RunwareAI API URL or Token not configured for model search.', ['provider_name' => $this->apiProvider->name]); return []; } $apiUrl = rtrim($this->apiProvider->api_url, '/'); $token = $this->apiProvider->token; try { $response = Http::withHeaders([ 'Authorization' => 'Bearer '.$token, 'Accept' => 'application/json', 'Content-Type' => 'application/json', ])->timeout(10)->post($apiUrl, [ [ 'taskType' => 'modelSearch', 'search' => $searchTerm, 'type' => 'base', 'category' => 'checkpoint', 'limit' => 100, 'taskUUID' => (string) Str::uuid(), ], ]); $responseData = $response->json(); if ($response->successful() && isset($responseData['data'][0]['results']) && ! isset($responseData['error'])) { $models = []; foreach ($responseData['data'][0]['results'] as $model) { $models[] = [ 'name' => $model['name'] ?? 'Unknown', 'id' => $model['air'] ?? 'Unknown', 'type' => $model['type'] ?? null, ]; } $this->logInfo('Model search successful on RunwareAI.', ['searchTerm' => $searchTerm, 'modelsFound' => count($models)]); return $models; } else { $errorMessage = $responseData['error'] ?? 'Unknown error'; $this->logError('Model search failed on RunwareAI: Unsuccessful response.', [ 'status' => $response->status(), 'response' => $responseData, 'error_message' => $errorMessage, ]); return []; } } catch (\Exception $e) { $this->logError('Model search failed on RunwareAI: Exception caught.', ['error' => $e->getMessage()]); return []; } } private function upload(string $imagePath): array { $this->logInfo('Attempting to upload image to RunwareAI.', ['image_path' => $imagePath]); if (! $this->apiProvider->api_url || ! $this->apiProvider->token) { $this->logError('RunwareAI API URL or Token not configured for upload.', ['provider_name' => $this->apiProvider->name]); throw new \Exception('RunwareAI API URL or Token not configured.'); } $apiUrl = rtrim($this->apiProvider->api_url, '/'); $token = $this->apiProvider->token; $taskUUID = (string) Str::uuid(); $imageData = 'data:image/png;base64,'.base64_encode(file_get_contents($imagePath)); try { $response = Http::withHeaders([ 'Authorization' => 'Bearer '.$token, 'Accept' => 'application/json', 'Content-Type' => 'application/json', ])->post($apiUrl, [ [ 'taskType' => 'imageUpload', 'taskUUID' => $taskUUID, 'image' => $imageData, ], ]); $response->throw(); $this->logInfo('Image uploaded successfully to RunwareAI.', ['task_uuid' => $taskUUID, 'response' => $response->json()]); return $response->json(); } catch (\Exception $e) { $this->logError('Image upload to RunwareAI failed.', ['error' => $e->getMessage(), 'image_path' => $imagePath]); throw $e; } } private function styleChangeRequest(\App\Models\Style $style, string $seedImageUUID): array { $this->logInfo('Attempting style change request to RunwareAI.', ['style_id' => $style->id, 'seed_image_uuid' => $seedImageUUID]); if (! $this->apiProvider->api_url || ! $this->apiProvider->token) { $this->logError('RunwareAI API URL or Token not configured for style change.', ['provider_name' => $this->apiProvider->name]); throw new \Exception('RunwareAI API URL or Token not configured.'); } $apiUrl = rtrim($this->apiProvider->api_url, '/'); $token = $this->apiProvider->token; $taskUUID = (string) Str::uuid(); $modelParams = $style->aiModel->parameters ?? []; $styleParams = $style->parameters ?? []; $mergedParams = array_replace_recursive($modelParams, $styleParams); $data = [ 'taskType' => 'imageInference', 'taskUUID' => $taskUUID, 'positivePrompt' => $style->prompt, 'seedImage' => $seedImageUUID, 'outputType' => 'base64Data', 'model' => $style->aiModel->model_id, ]; foreach ($mergedParams as $key => $value) { $data[$key] = $value; } try { $response = Http::withHeaders([ 'Authorization' => 'Bearer '.$token, 'Accept' => 'application/json', ])->post($apiUrl, [ $data, ]); $response->throw(); $responseData = $response->json(); if (! isset($responseData['data'][0]['imageBase64Data'])) { throw new \Exception('AI service did not return base64 image data.'); } $base64Image = $responseData['data'][0]['imageBase64Data']; $this->logInfo('Style change request successful to RunwareAI.', ['task_uuid' => $taskUUID, 'response' => $responseData]); return ['base64Data' => $base64Image]; } catch (\Exception $e) { $errorData = []; /*if ($e instanceof \Illuminate\Http\Client\RequestException && $e->response) { $errorData['response_body'] = $e->response->body(); $errorData['response_status'] = $e->response->status(); }*/ $this->logError('Style change request to RunwareAI failed.', ['error' => $e->getMessage(), 'task_uuid' => $taskUUID] + $errorData); throw $e; } } }