Runware/ComfyUI fixes, dashboard links, import action, Leonardo plugin, widget, added status field and test connection button

This commit is contained in:
2025-12-03 14:48:45 +01:00
parent 3ec8e471bc
commit 090ec2c44b
16 changed files with 1019 additions and 142 deletions

View File

@@ -4,6 +4,7 @@ namespace App\Api\Plugins;
use App\Models\ApiProvider;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class ComfyUi implements ApiPluginInterface
{
@@ -41,6 +42,7 @@ class ComfyUi implements ApiPluginInterface
} else {
$this->logError('Failed to enable ComfyUi plugin.', ['provider_name' => $this->apiProvider->name]);
}
return $result;
}
@@ -53,12 +55,14 @@ class ComfyUi implements ApiPluginInterface
} else {
$this->logError('Failed to disable ComfyUi plugin.', ['provider_name' => $this->apiProvider->name]);
}
return $result;
}
public function getStatus(string $imageUUID): array
{
$this->logDebug('Getting status for image.', ['image_uuid' => $imageUUID]);
// Implement ComfyUI specific status check
return ['status' => 'unknown'];
}
@@ -66,6 +70,7 @@ class ComfyUi implements ApiPluginInterface
public function getProgress(string $imageUUID): array
{
$this->logDebug('Progress updates are handled via WebSocket.', ['image_uuid' => $imageUUID]);
return ['progress' => 0]; // Progress is now handled by WebSocket
}
@@ -74,11 +79,12 @@ class ComfyUi implements ApiPluginInterface
// This method is no longer used for progress polling, but might be used for final result retrieval
$apiUrl = rtrim($this->apiProvider->api_url, '/');
$timeout = 60; // seconds
$this->logDebug('ComfyUI History API URL:', ['url' => $apiUrl . '/history/' . $promptId, 'timeout' => $timeout]);
$response = Http::timeout($timeout)->get($apiUrl . '/history/' . $promptId);
$this->logDebug('ComfyUI History API URL:', ['url' => $apiUrl.'/history/'.$promptId, 'timeout' => $timeout]);
$response = Http::timeout($timeout)->get($apiUrl.'/history/'.$promptId);
if ($response->failed()) {
throw new \Exception('Failed to get history from ComfyUI');
}
return $response->json();
}
@@ -87,7 +93,7 @@ class ComfyUi implements ApiPluginInterface
$this->logInfo('Starting ComfyUI style change process.', ['image_id' => $image->id, 'style_id' => $style->id]);
// 1. Upload image to ComfyUI
$uploadResponse = $this->uploadImage(public_path('storage/' . $image->path));
$uploadResponse = $this->uploadImage(public_path('storage/'.$image->path));
$filename = $uploadResponse['name'];
// 2. Construct the prompt
@@ -106,7 +112,7 @@ class ComfyUi implements ApiPluginInterface
$this->logInfo('Uploading image to ComfyUI.', ['image_path' => $imagePath]);
$response = Http::attach(
'image', file_get_contents($imagePath), basename($imagePath)
)->timeout(60)->post(rtrim($this->apiProvider->api_url, '/') . '/upload/image', [
)->timeout(60)->post(rtrim($this->apiProvider->api_url, '/').'/upload/image', [
'type' => 'input',
'overwrite' => 'false',
]);
@@ -131,7 +137,7 @@ class ComfyUi implements ApiPluginInterface
$modelParams = [];
}
}
if (is_string($styleParams)) {
$styleParams = json_decode($styleParams, true);
if (json_last_error() !== JSON_ERROR_NONE) {
@@ -161,9 +167,9 @@ class ComfyUi implements ApiPluginInterface
if (json_last_error() !== JSON_ERROR_NONE) {
$this->logError('Failed to decode workflow JSON after placeholder replacement.', [
'json_error' => json_last_error_msg(),
'workflow_string' => $workflow
'workflow_string' => $workflow,
]);
throw new \Exception('Failed to construct valid ComfyUI workflow JSON: ' . json_last_error_msg());
throw new \Exception('Failed to construct valid ComfyUI workflow JSON: '.json_last_error_msg());
}
return $decodedWorkflow;
@@ -172,7 +178,7 @@ class ComfyUi implements ApiPluginInterface
private function queuePrompt(array $promptData): array
{
$this->logInfo('Queueing prompt in ComfyUI.');
$response = Http::timeout(60)->post(rtrim($this->apiProvider->api_url, '/') . '/prompt', ['prompt' => $promptData]);
$response = Http::timeout(60)->post(rtrim($this->apiProvider->api_url, '/').'/prompt', ['prompt' => $promptData]);
if ($response->failed()) {
$this->logError('Failed to queue prompt in ComfyUI.', ['response' => $response->body()]);
@@ -196,7 +202,7 @@ class ComfyUi implements ApiPluginInterface
}
try {
$response = Http::timeout(60)->get(rtrim($this->apiProvider->api_url, '/') . '/history/' . $promptId);
$response = Http::timeout(60)->get(rtrim($this->apiProvider->api_url, '/').'/history/'.$promptId);
$this->logDebug('waitForResult: History API response status.', ['status' => $response->status(), 'prompt_id' => $promptId]);
if ($response->failed()) {
@@ -219,7 +225,7 @@ class ComfyUi implements ApiPluginInterface
$output['images'][0]['subfolder']
);
$this->logInfo('waitForResult: Constructed image URL.', ['imageUrl' => $imageUrl, 'prompt_id' => $promptId]);
$imageResponse = Http::timeout(60)->get($imageUrl);
$this->logDebug('waitForResult: Image fetch response status.', ['status' => $imageResponse->status(), 'imageUrl' => $imageUrl]);
@@ -228,6 +234,7 @@ class ComfyUi implements ApiPluginInterface
throw new \Exception('Failed to retrieve image data from ComfyUI');
}
$this->logInfo('waitForResult: Successfully retrieved image data.', ['prompt_id' => $promptId]);
return base64_encode($imageResponse->body());
}
}
@@ -245,13 +252,31 @@ class ComfyUi implements ApiPluginInterface
public function testConnection(array $data): bool
{
$apiUrl = rtrim($data['api_url'], '/');
$apiUrl = rtrim($data['api_url'] ?? '', '/');
if (! $apiUrl) {
throw new \RuntimeException('ComfyUI API-URL fehlt.');
}
try {
$response = Http::timeout(5)->get($apiUrl . '/queue');
return $response->successful();
$response = Http::timeout(5)->get($apiUrl.'/queue');
if ($response->successful()) {
return true;
}
$body = $response->body();
$snippet = $body ? Str::limit($body, 200) : null;
$this->logError('ComfyUI connection test failed: HTTP error.', [
'status' => $response->status(),
'body' => $snippet,
]);
throw new \RuntimeException('ComfyUI HTTP '.$response->status().($snippet ? ': '.$snippet : ''));
} catch (\Exception $e) {
$this->logError('ComfyUI connection test failed.', ['error' => $e->getMessage()]);
return false;
throw $e;
}
}
@@ -259,52 +284,57 @@ class ComfyUi implements ApiPluginInterface
{
$this->logInfo('Checking ComfyUI availability.');
if (!$this->apiProvider->enabled) {
if (! $this->apiProvider->enabled) {
$this->logDebug('ComfyUI provider is disabled.');
return [
'available' => false,
'reason' => 'Provider is disabled',
'provider_id' => $this->apiProvider->id,
'provider_name' => $this->apiProvider->name
'provider_name' => $this->apiProvider->name,
];
}
if (empty($this->apiProvider->api_url)) {
$this->logDebug('ComfyUI API URL is not configured.');
return [
'available' => false,
'reason' => 'API URL not configured',
'provider_id' => $this->apiProvider->id,
'provider_name' => $this->apiProvider->name
'provider_name' => $this->apiProvider->name,
];
}
try {
$response = Http::timeout(5)->get(rtrim($this->apiProvider->api_url, '/') . '/queue');
$response = Http::timeout(5)->get(rtrim($this->apiProvider->api_url, '/').'/queue');
if ($response->successful()) {
$this->logInfo('ComfyUI is available.');
return [
'available' => true,
'reason' => 'Connection successful',
'provider_id' => $this->apiProvider->id,
'provider_name' => $this->apiProvider->name
'provider_name' => $this->apiProvider->name,
];
} else {
$this->logError('ComfyUI connection failed.', ['status' => $response->status()]);
return [
'available' => false,
'reason' => 'Connection failed: ' . $response->status(),
'reason' => 'Connection failed: '.$response->status(),
'provider_id' => $this->apiProvider->id,
'provider_name' => $this->apiProvider->name
'provider_name' => $this->apiProvider->name,
];
}
} catch (\Exception $e) {
$this->logError('ComfyUI availability check failed.', ['error' => $e->getMessage()]);
return [
'available' => false,
'reason' => 'Connection error: ' . $e->getMessage(),
'reason' => 'Connection error: '.$e->getMessage(),
'provider_id' => $this->apiProvider->id,
'provider_name' => $this->apiProvider->name
'provider_name' => $this->apiProvider->name,
];
}
}
@@ -312,11 +342,12 @@ class ComfyUi implements ApiPluginInterface
public function searchModels(string $searchTerm): array
{
$this->logInfo('ComfyUI does not support model search. Returning empty list.', ['searchTerm' => $searchTerm]);
return [];
}
public function getStyledImage(string $promptId): string
{
return $this->waitForResult($promptId);
}
}
}

View File

@@ -0,0 +1,270 @@
<?php
namespace App\Api\Plugins;
use App\Models\ApiProvider;
use App\Models\Image;
use App\Models\Style;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use RuntimeException;
class LeonardoAi implements ApiPluginInterface
{
use LoggablePlugin;
private const BASE_URL = 'https://cloud.leonardo.ai/api/rest/v2';
private const SUPPORTED_MODELS = [
'gemini-image-2', // Nano Banana Pro
'flux-pro-2.0', // FLUX.2 Pro
'seedream-4.0', // Seedream 4.0
];
public function __construct(private ApiProvider $apiProvider) {}
public function getIdentifier(): string
{
return 'leonardoai';
}
public function getName(): string
{
return 'Leonardo AI';
}
public function isEnabled(): bool
{
return $this->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.');
}
}
}

View File

@@ -42,6 +42,7 @@ class RunwareAi implements ApiPluginInterface
} else {
$this->logError('Failed to enable RunwareAi plugin.', ['provider_name' => $this->apiProvider->name]);
}
return $result;
}
@@ -54,9 +55,10 @@ class RunwareAi implements ApiPluginInterface
} 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.');
@@ -65,6 +67,7 @@ class RunwareAi implements ApiPluginInterface
public function getStatus(string $imageUUID): array
{
$this->logDebug('Getting status for image.', ['image_uuid' => $imageUUID]);
// Implement RunwareAI specific status check
return ['status' => 'unknown'];
}
@@ -72,6 +75,7 @@ class RunwareAi implements ApiPluginInterface
public function getProgress(string $imageUUID): array
{
$this->logDebug('Getting progress for image.', ['image_uuid' => $imageUUID]);
// Implement RunwareAI specific progress check
return ['progress' => 0];
}
@@ -79,9 +83,9 @@ class RunwareAi implements ApiPluginInterface
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));
$uploadResult = $this->upload(public_path('storage/'.$image->path));
if (!isset($uploadResult['data'][0]['imageUUID'])) {
if (! isset($uploadResult['data'][0]['imageUUID'])) {
throw new \Exception('Image upload to AI service failed or returned no UUID.');
}
$seedImageUUID = $uploadResult['data'][0]['imageUUID'];
@@ -89,7 +93,7 @@ class RunwareAi implements ApiPluginInterface
// Step 2: Request style change using the uploaded image's UUID
$result = $this->styleChangeRequest($style, $seedImageUUID);
if (!isset($result['base64Data'])) {
if (! isset($result['base64Data'])) {
throw new \Exception('AI service did not return base64 image data.');
}
@@ -101,41 +105,60 @@ class RunwareAi implements ApiPluginInterface
$apiUrl = rtrim($data['api_url'], '/');
$token = $data['token'] ?? null;
if (!$apiUrl || !$token) {
if (! $apiUrl || ! $token) {
$this->logError('RunwareAI connection test failed: API URL or Token missing.');
return false;
throw new \RuntimeException('API URL oder Token fehlt.');
}
try {
$response = Http::withHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json',
])->timeout(5)->post($apiUrl, [
$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();
if ($response->successful() && isset($responseData['data']) && !isset($responseData['error'])) {
$this->logInfo('RunwareAI connection test successful: Authentication successful.', [
// 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(),
'response' => $responseData,
'error' => $error,
]);
return true;
} else {
$errorMessage = $responseData['error'] ?? 'Unknown error';
$this->logError('RunwareAI connection test failed: Authentication failed.', [
'status' => $response->status(),
'response' => $responseData,
'error_message' => $errorMessage,
]);
return false;
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()]);
return false;
throw $e;
}
}
@@ -143,33 +166,36 @@ class RunwareAi implements ApiPluginInterface
{
$this->logInfo('Checking RunwareAI availability.');
if (!$this->apiProvider->enabled) {
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
'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
'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
'provider_name' => $this->apiProvider->name,
];
}
@@ -185,44 +211,47 @@ class RunwareAi implements ApiPluginInterface
$responseData = $response->json();
if ($response->successful() && isset($responseData['data']) && !isset($responseData['error'])) {
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
'provider_name' => $this->apiProvider->name,
];
} else {
$errorMessage = $responseData['error'] ?? 'Unknown error';
$this->logError('RunwareAI connection failed.', [
'status' => $response->status(),
'error_message' => $errorMessage
'error_message' => $errorMessage,
]);
return [
'available' => false,
'reason' => 'Connection failed: ' . $errorMessage,
'reason' => 'Connection failed: '.$errorMessage,
'provider_id' => $this->apiProvider->id,
'provider_name' => $this->apiProvider->name
'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(),
'reason' => 'Connection error: '.$e->getMessage(),
'provider_id' => $this->apiProvider->id,
'provider_name' => $this->apiProvider->name
'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) {
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 [];
}
@@ -231,7 +260,7 @@ class RunwareAi implements ApiPluginInterface
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $token,
'Authorization' => 'Bearer '.$token,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
])->timeout(10)->post($apiUrl, [
@@ -242,12 +271,12 @@ class RunwareAi implements ApiPluginInterface
'category' => 'checkpoint',
'limit' => 100,
'taskUUID' => (string) Str::uuid(),
]
],
]);
$responseData = $response->json();
if ($response->successful() && isset($responseData['data'][0]['results']) && !isset($responseData['error'])) {
if ($response->successful() && isset($responseData['data'][0]['results']) && ! isset($responseData['error'])) {
$models = [];
foreach ($responseData['data'][0]['results'] as $model) {
$models[] = [
@@ -257,6 +286,7 @@ class RunwareAi implements ApiPluginInterface
];
}
$this->logInfo('Model search successful on RunwareAI.', ['searchTerm' => $searchTerm, 'modelsFound' => count($models)]);
return $models;
} else {
$errorMessage = $responseData['error'] ?? 'Unknown error';
@@ -265,10 +295,12 @@ class RunwareAi implements ApiPluginInterface
'response' => $responseData,
'error_message' => $errorMessage,
]);
return [];
}
} catch (\Exception $e) {
$this->logError('Model search failed on RunwareAI: Exception caught.', ['error' => $e->getMessage()]);
return [];
}
}
@@ -276,7 +308,7 @@ class RunwareAi implements ApiPluginInterface
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) {
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.');
}
@@ -285,11 +317,11 @@ class RunwareAi implements ApiPluginInterface
$token = $this->apiProvider->token;
$taskUUID = (string) Str::uuid();
$imageData = 'data:image/png;base64,' . base64_encode(file_get_contents($imagePath));
$imageData = 'data:image/png;base64,'.base64_encode(file_get_contents($imagePath));
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $token,
'Authorization' => 'Bearer '.$token,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
])->post($apiUrl, [
@@ -297,11 +329,12 @@ class RunwareAi implements ApiPluginInterface
'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]);
@@ -312,7 +345,7 @@ class RunwareAi implements ApiPluginInterface
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) {
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.');
}
@@ -331,7 +364,7 @@ class RunwareAi implements ApiPluginInterface
'positivePrompt' => $style->prompt,
'seedImage' => $seedImageUUID,
'outputType' => 'base64Data',
'model' => $style->aiModel->model_id
'model' => $style->aiModel->model_id,
];
foreach ($mergedParams as $key => $value) {
@@ -340,22 +373,23 @@ class RunwareAi implements ApiPluginInterface
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $token,
'Authorization' => 'Bearer '.$token,
'Accept' => 'application/json',
])->post($apiUrl, [
$data
$data,
]);
$response->throw();
$responseData = $response->json();
if (!isset($responseData['data'][0]['imageBase64Data'])) {
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 = [];
@@ -367,4 +401,4 @@ class RunwareAi implements ApiPluginInterface
throw $e;
}
}
}
}