Files
ai-stylegallery/app/Api/Plugins/RunwareAi.php

405 lines
14 KiB
PHP

<?php
namespace App\Api\Plugins;
use App\Models\ApiProvider;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class RunwareAi implements ApiPluginInterface
{
use LoggablePlugin;
protected $apiProvider;
public function __construct(ApiProvider $apiProvider)
{
$this->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;
}
}
}