upgrade to laravel 12 done

This commit is contained in:
2025-08-03 21:33:05 +02:00
parent 83aba4c1a8
commit b4456bcabb
41 changed files with 3368 additions and 1455 deletions

View File

@@ -65,9 +65,21 @@ class ComfyUi implements ApiPluginInterface
public function getProgress(string $imageUUID): array public function getProgress(string $imageUUID): array
{ {
$this->logDebug('Getting progress for image.', ['image_uuid' => $imageUUID]); $this->logDebug('Progress updates are handled via WebSocket.', ['image_uuid' => $imageUUID]);
// Implement ComfyUI specific progress check return ['progress' => 0]; // Progress is now handled by WebSocket
return ['progress' => 0]; }
private function getHistory(string $promptId): array
{
// 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);
if ($response->failed()) {
throw new \Exception('Failed to get history from ComfyUI');
}
return $response->json();
} }
public function processImageStyleChange(string $imagePath, string $prompt, string $modelId, ?string $parameters = null): array public function processImageStyleChange(string $imagePath, string $prompt, string $modelId, ?string $parameters = null): array
@@ -85,10 +97,8 @@ class ComfyUi implements ApiPluginInterface
$queueResponse = $this->queuePrompt($promptData); $queueResponse = $this->queuePrompt($promptData);
$promptId = $queueResponse['prompt_id']; $promptId = $queueResponse['prompt_id'];
// 4. Wait for and get the result // Return the prompt_id for frontend WebSocket tracking
$result = $this->waitForResult($promptId); return ['prompt_id' => $promptId];
return ['base64Data' => $result];
} }
private function uploadImage(string $imagePath): array private function uploadImage(string $imagePath): array
@@ -96,7 +106,7 @@ class ComfyUi implements ApiPluginInterface
$this->logInfo('Uploading image to ComfyUI.', ['image_path' => $imagePath]); $this->logInfo('Uploading image to ComfyUI.', ['image_path' => $imagePath]);
$response = Http::attach( $response = Http::attach(
'image', file_get_contents($imagePath), basename($imagePath) 'image', file_get_contents($imagePath), basename($imagePath)
)->timeout(120)->post(rtrim($this->apiProvider->api_url, '/') . '/upload/image', [ )->timeout(60)->post(rtrim($this->apiProvider->api_url, '/') . '/upload/image', [
'type' => 'input', 'type' => 'input',
'overwrite' => 'false', 'overwrite' => 'false',
]); ]);
@@ -126,7 +136,7 @@ class ComfyUi implements ApiPluginInterface
private function queuePrompt(array $promptData): array private function queuePrompt(array $promptData): array
{ {
$this->logInfo('Queueing prompt in ComfyUI.'); $this->logInfo('Queueing prompt in ComfyUI.');
$response = Http::timeout(120)->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()) { if ($response->failed()) {
$this->logError('Failed to queue prompt in ComfyUI.', ['response' => $response->body()]); $this->logError('Failed to queue prompt in ComfyUI.', ['response' => $response->body()]);
@@ -136,30 +146,64 @@ class ComfyUi implements ApiPluginInterface
return $response->json(); return $response->json();
} }
private function waitForResult(string $promptId): string public function waitForResult(string $promptId): string
{ {
$this->logInfo('Waiting for ComfyUI result.', ['prompt_id' => $promptId]); set_time_limit(120); // Set maximum execution time for this function
while (true) { $this->logInfo('waitForResult: Waiting for ComfyUI result.', ['prompt_id' => $promptId]);
$response = Http::timeout(120)->get($this->apiProvider->api_url . '/history/' . $promptId); $startTime = microtime(true);
$data = $response->json(); $timeout = 180; // seconds
if (!empty($data[$promptId]['outputs'])) { while (true) {
$outputs = $data[$promptId]['outputs']; if (microtime(true) - $startTime > $timeout) {
// Assuming the first output with an image is the one we want $this->logError('waitForResult: ComfyUI result polling timed out.', ['prompt_id' => $promptId]);
foreach ($outputs as $output) { throw new \Exception('ComfyUI result polling timed out.');
if (isset($output['images'][0]['type']) && $output['images'][0]['type'] === 'output') {
$imageUrl = sprintf('%s/view?filename=%s&subfolder=%s&type=output',
$this->apiProvider->api_url,
$output['images'][0]['filename'],
$output['images'][0]['subfolder']
);
$image_data = file_get_contents($imageUrl);
return base64_encode($image_data);
}
}
} }
sleep(2); // Wait for 2 seconds before polling again try {
$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()) {
$this->logError('waitForResult: Failed to get history from ComfyUI.', ['prompt_id' => $promptId, 'response' => $response->body()]);
throw new \Exception('Failed to get history from ComfyUI');
}
$data = $response->json();
$this->logDebug('waitForResult: History API response data.', ['data' => $data, 'prompt_id' => $promptId]);
if (isset($data[$promptId]['outputs'])) {
$outputs = $data[$promptId]['outputs'];
$this->logInfo('waitForResult: Found outputs in history.', ['prompt_id' => $promptId, 'outputs_count' => count($outputs)]);
foreach ($outputs as $output) {
if (isset($output['images'][0]['type']) && $output['images'][0]['type'] === 'output') {
$imageUrl = sprintf('%s/view?filename=%s&subfolder=%s&type=output',
rtrim($this->apiProvider->api_url, '/'),
$output['images'][0]['filename'],
$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]);
if ($imageResponse->failed()) {
$this->logError('waitForResult: Failed to retrieve image data from ComfyUI.', ['imageUrl' => $imageUrl, 'response' => $imageResponse->body()]);
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());
}
}
} else {
$this->logDebug('waitForResult: No outputs found yet for prompt.', ['prompt_id' => $promptId]);
}
} catch (\Exception $e) {
$this->logError('waitForResult: Exception caught during polling.', ['prompt_id' => $promptId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
throw $e; // Re-throw the exception to be caught by ImageController
}
usleep(500000); // Wait for 0.5 seconds before polling again
} }
} }
} }

View File

@@ -1,27 +0,0 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule): void
{
// $schedule->command('inspire')->hourly();
}
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@@ -33,7 +33,7 @@ class CollectionEloquentBuilder extends Builder
return $this->collection->firstWhere('id', $id); return $this->collection->firstWhere('id', $id);
} }
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null) public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null, $total = null)
{ {
$page = $page ?: Paginator::resolveCurrentPage($pageName); $page = $page ?: Paginator::resolveCurrentPage($pageName);

View File

@@ -45,10 +45,11 @@ class StyleResource extends Resource
->rows(5), ->rows(5),
FileUpload::make('preview_image') FileUpload::make('preview_image')
->label(__('filament.resource.style.form.preview_image')) ->label(__('filament.resource.style.form.preview_image'))
->required()
->image()
->disk('public') ->disk('public')
->directory('style_previews'), ->directory('style_previews')
->image()
->required()
->rules(['mimes:jpeg,png,bmp,gif,webp']),
Textarea::make('parameters') Textarea::make('parameters')
->label(__('filament.resource.style.form.parameters')) ->label(__('filament.resource.style.form.parameters'))
->nullable() ->nullable()

View File

@@ -9,6 +9,7 @@ use App\Models\ApiProvider;
use App\Models\Style; use App\Models\Style;
use App\Models\Image; use App\Models\Image;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon; use Carbon\Carbon;
use App\Models\Setting; use App\Models\Setting;
@@ -75,7 +76,7 @@ class ImageController extends Controller
public function upload(Request $request) public function upload(Request $request)
{ {
$request->validate([ $request->validate([
'image' => 'required|image|max:10240', // Max 10MB 'image' => 'required|image|mimes:jpeg,png,bmp,gif,webp|max:10240', // Max 10MB
]); ]);
$file = $request->file('image'); $file = $request->file('image');
@@ -156,34 +157,16 @@ class ImageController extends Controller
$style->parameters $style->parameters
); );
$base64Image = $result['base64Data']; // Update the image model with the ComfyUI prompt_id and style_id
$decodedImage = base64_decode(preg_replace('#^data:image/\w+;base64, #i', '', $base64Image)); $image->comfyui_prompt_id = $result['prompt_id'];
$image->style_id = $style->id;
$newImageName = 'styled_' . uniqid() . '.png'; // Assuming PNG for now $image->save();
$newImagePathRelative = 'uploads/' . $newImageName; // Path relative to public/storage/
$newImageFullPath = public_path('storage/' . $newImagePathRelative); // Full path to save
// Ensure the directory exists
if (!File::exists(public_path('storage/uploads'))) {
File::makeDirectory(public_path('storage/uploads'), 0755, true);
}
File::put($newImageFullPath, $decodedImage); // Save using File facade
$newImage = Image::create([
'path' => $newImagePathRelative, // Store relative path
'original_image_id' => $image->id, // Link to original image
'style_id' => $style->id, // Link to applied style
'is_temp' => true, // Mark as temporary until user keeps it
]);
// Return the prompt_id for WebSocket tracking
return response()->json([ return response()->json([
'message' => 'Style change successful', 'message' => 'Style change request sent.',
'styled_image' => [ 'prompt_id' => $result['prompt_id'],
'id' => $newImage->id, 'image_uuid' => $image->uuid, // Pass image UUID for frontend tracking
'path' => asset('storage/' . $newImage->path),
'is_temp' => $newImage->is_temp,
],
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 500); return response()->json(['error' => $e->getMessage()], 500);
@@ -243,26 +226,99 @@ class ImageController extends Controller
} }
} }
public function getProgress(Request $request) public function fetchStyledImage(string $promptId)
{ {
$request->validate([ Log::info('fetchStyledImage called.', ['prompt_id' => $promptId]);
'image_id' => 'required|exists:images,id',
'api_provider_name' => 'required|string',
]);
$image = Image::find($request->image_id);
$apiProvider = ApiProvider::where('name', $request->api_provider_name)->first();
if (!$image || !$apiProvider) {
return response()->json(['error' => __('api.image_or_provider_not_found')], 404);
}
try { try {
$plugin = PluginLoader::getPlugin($apiProvider->name); // Find the image associated with the prompt_id, eagerly loading relationships
$progress = $plugin->getProgress($image->uuid); // Annahme: Image Model hat eine UUID $image = Image::with(['style.aiModel.apiProviders' => function ($query) {
return response()->json($progress); $query->where('enabled', true);
}])->where('comfyui_prompt_id', $promptId)->first();
if (!$image) {
Log::warning('fetchStyledImage: Image not found for prompt_id.', ['prompt_id' => $promptId]);
return response()->json(['error' => __('api.image_not_found')], 404);
}
Log::info('fetchStyledImage: Image found.', ['image_id' => $image->id, 'image_uuid' => $image->uuid, 'comfyui_prompt_id' => $image->comfyui_prompt_id]);
// Get the style and API provider associated with the image
$style = $image->style;
if (!$style) {
Log::warning('fetchStyledImage: Style not found for image.', ['image_id' => $image->id]);
return response()->json(['error' => __('api.style_or_provider_not_found')], 404);
}
Log::info('fetchStyledImage: Style found.', ['style_id' => $style->id, 'style_name' => $style->title]);
if (!$style->aiModel) {
Log::warning('fetchStyledImage: AI Model not found for style.', ['style_id' => $style->id]);
return response()->json(['error' => __('api.style_or_provider_not_found')], 404);
}
Log::info('fetchStyledImage: AI Model found.', ['ai_model_id' => $style->aiModel->id, 'ai_model_name' => $style->aiModel->name]);
if ($style->aiModel->apiProviders->isEmpty()) {
Log::warning('fetchStyledImage: No enabled API Providers found for AI Model.', ['ai_model_id' => $style->aiModel->id]);
return response()->json(['error' => __('api.style_or_provider_not_found')], 404);
}
$apiProvider = $style->aiModel->apiProviders->first();
Log::info('fetchStyledImage: API Provider found.', ['api_provider_id' => $apiProvider->id, 'api_provider_name' => $apiProvider->name]);
Log::info('Fetching base64 image from plugin.', ['prompt_id' => $promptId, 'api_provider' => $apiProvider->name]);
// Use the plugin to get the final image data (e.g., from ComfyUI's history/view)
$plugin = PluginLoader::getPlugin($apiProvider->plugin, $apiProvider);
$base64Image = $plugin->waitForResult($promptId); // Re-purpose waitForResult for final fetch
if (empty($base64Image)) {
Log::error('Received empty base64 image from plugin.', ['prompt_id' => $promptId]);
return response()->json(['error' => 'Received empty image data.'], 500);
}
Log::info('Base64 image received. Decoding and saving.');
$decodedImage = base64_decode(preg_replace('#^data:image/\w+;base64, #i', '', $base64Image));
$newImageName = 'styled_' . uniqid() . '.png';
$newImagePathRelative = 'uploads/' . $newImageName;
$newImageFullPath = public_path('storage/' . $newImagePathRelative);
if (!File::exists(public_path('storage/uploads'))) {
File::makeDirectory(public_path('storage/uploads'), 0755, true);
Log::info('Created uploads directory.', ['path' => public_path('storage/uploads')]);
}
File::put($newImageFullPath, $decodedImage); // Save using File facade
Log::info('Image saved to disk.', ['path' => $newImageFullPath]);
$newImage = Image::create([
'path' => $newImagePathRelative, // Store relative path
'original_image_id' => $image->id,
'style_id' => $style->id,
'is_temp' => true,
]);
Log::info('New image record created in database.', ['image_id' => $newImage->id, 'path' => $newImage->path]);
return response()->json([
'message' => 'Styled image fetched successfully',
'styled_image' => [
'id' => $newImage->id,
'path' => asset('storage/' . $newImage->path),
'is_temp' => $newImage->is_temp,
],
]);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Error in fetchStyledImage: ' . $e->getMessage(), ['exception' => $e]);
return response()->json(['error' => $e->getMessage()], 500); return response()->json(['error' => $e->getMessage()], 500);
} }
} }
}
public function getComfyUiUrl()
{
$apiProvider = ApiProvider::where('plugin', 'comfyui')->where('enabled', true)->first();
if (!$apiProvider) {
return response()->json(['error' => 'No enabled ComfyUI API provider found.'], 404);
}
return response()->json(['comfyui_url' => rtrim($apiProvider->api_url, '/')]);
}
}

View File

@@ -1,70 +0,0 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\SetContentSecurityPolicy::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\HandleInertiaRequests::class,
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's middleware aliases.
*
* Aliases may be used to conveniently assign middleware to routes and groups.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}

View File

@@ -17,5 +17,11 @@ class Image extends Model
'style_id', 'style_id',
'is_temp', 'is_temp',
'is_public', 'is_public',
'comfyui_prompt_id',
]; ];
public function style()
{
return $this->belongsTo(Style::class);
}
} }

View File

@@ -1,55 +1,35 @@
<?php <?php
/* use Illuminate\Foundation\Application;
|-------------------------------------------------------------------------- use Illuminate\Foundation\Configuration\Exceptions;
| Create The Application use Illuminate\Foundation\Configuration\Middleware;
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
$app = new Illuminate\Foundation\Application( return Application::configure(basePath: dirname(__DIR__))
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) ->withRouting(
); web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
]);
/* $middleware->alias([
|-------------------------------------------------------------------------- 'auth' => \App\Http\Middleware\Authenticate::class,
| Bind Important Interfaces 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|-------------------------------------------------------------------------- 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
| 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
| Next, we need to bind some important interfaces into the container so 'can' => \Illuminate\Auth\Middleware\Authorize::class,
| we will be able to resolve them when needed. The kernels serve the 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
| incoming requests to this application from both the web and CLI. 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
| 'signed' => \App\Http\Middleware\ValidateSignature::class,
*/ 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
$app->singleton( ]);
Illuminate\Contracts\Http\Kernel::class, })
App\Http\Kernel::class ->withExceptions(function (Exceptions $exceptions) {
); //
})->create();
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;

View File

@@ -5,14 +5,16 @@
"keywords": ["framework", "laravel"], "keywords": ["framework", "laravel"],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.1", "php": "^8.3",
"filament/filament": "3.0", "amphp/websocket-client": "^2.0",
"filament/filament": "^3.2",
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"inertiajs/inertia-laravel": "^0.6.8", "inertiajs/inertia-laravel": "^1.0",
"laravel/breeze": "1.29", "laravel/breeze": "^2.0",
"laravel/framework": "^10.0", "laravel/framework": "^12.0",
"laravel/sanctum": "^3.2", "laravel/sanctum": "^4.0",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"predis/predis": "^3.1",
"tightenco/ziggy": "^2.0" "tightenco/ziggy": "^2.0"
}, },
"require-dev": { "require-dev": {
@@ -20,9 +22,9 @@
"laravel/pint": "^1.0", "laravel/pint": "^1.0",
"laravel/sail": "^1.18", "laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4", "mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0", "nunomaduro/collision": "^8.1",
"phpunit/phpunit": "^10.0", "phpunit/phpunit": "^12.0",
"spatie/laravel-ignition": "^2.0" "spatie/laravel-ignition": "^2.7"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

3636
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ return [
| |
*/ */
'default' => env('BROADCAST_DRIVER', 'null'), 'default' => env('BROADCAST_DRIVER', 'redis'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('images', function (Blueprint $table) {
$table->string('comfyui_prompt_id')->nullable()->after('uuid');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('images', function (Blueprint $table) {
$table->dropColumn('comfyui_prompt_id');
});
}
};

149
package-lock.json generated
View File

@@ -7,7 +7,9 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^7.0.0", "@fortawesome/fontawesome-svg-core": "^7.0.0",
"@fortawesome/free-solid-svg-icons": "^7.0.0", "@fortawesome/free-solid-svg-icons": "^7.0.0",
"@fortawesome/vue-fontawesome": "^3.1.1" "@fortawesome/vue-fontawesome": "^3.1.1",
"laravel-echo": "^2.1.7",
"pusher-js": "^8.4.0"
}, },
"devDependencies": { "devDependencies": {
"@inertiajs/vue3": "^1.0.0", "@inertiajs/vue3": "^1.0.0",
@@ -928,6 +930,13 @@
"win32" "win32"
] ]
}, },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT",
"peer": true
},
"node_modules/@tailwindcss/forms": { "node_modules/@tailwindcss/forms": {
"version": "0.5.10", "version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
@@ -1426,6 +1435,24 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/deepmerge": { "node_modules/deepmerge": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -1496,6 +1523,30 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/engine.io-client": {
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/entities": { "node_modules/entities": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -1992,6 +2043,19 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/laravel-echo": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.1.7.tgz",
"integrity": "sha512-EMcRmI/hJhea+eCeyJY9e7gkdVVZ42kGyntKZuM3NxeR00Glu45Cpsr0kNz4QC09YCQd48LZjQA1xSTtYteTwg==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"pusher-js": "*",
"socket.io-client": "*"
}
},
"node_modules/laravel-vite-plugin": { "node_modules/laravel-vite-plugin": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.3.0.tgz", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.3.0.tgz",
@@ -2156,6 +2220,13 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT",
"peer": true
},
"node_modules/mz": { "node_modules/mz": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -2489,6 +2560,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pusher-js": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz",
"integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==",
"license": "MIT",
"dependencies": {
"tweetnacl": "^1.0.3"
}
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.0", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
@@ -2757,6 +2837,36 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/socket.io-client": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"license": "MIT",
"peer": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -2987,6 +3097,12 @@
"dev": true, "dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"license": "Unlicense"
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
@@ -3231,6 +3347,37 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.8.0", "version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",

View File

@@ -19,6 +19,8 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^7.0.0", "@fortawesome/fontawesome-svg-core": "^7.0.0",
"@fortawesome/free-solid-svg-icons": "^7.0.0", "@fortawesome/free-solid-svg-icons": "^7.0.0",
"@fortawesome/vue-fontawesome": "^3.1.1" "@fortawesome/vue-fontawesome": "^3.1.1",
"laravel-echo": "^2.1.7",
"pusher-js": "^8.4.0"
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}.tippy-box[data-theme~=light]{color:#26323d;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;background-color:#fff}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff} .fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
function i({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0&&this.addRow(),this.shouldUpdateRows=!0,this.$watch("state",()=>{if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState(),this.shouldUpdateRows=!0},reorderRows:function(t){let e=Alpine.raw(this.rows),s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.rows=e,this.updateState()},updateRows:function(){let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{i as default}; function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},input:{["x-on:blur"]:"createTag()",["x-model"]:"newTag",["x-on:keydown"](t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},["x-on:paste"](){$nextTick(()=>{let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default};

View File

@@ -1 +1 @@
function t({initialHeight:e}){return{render:function(){this.$el.scrollHeight>0&&(this.$el.style.height=e+"rem",this.$el.style.height=this.$el.scrollHeight+"px")}}}export{t as default}; function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
function d(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){this.isLoading=!0;let t=await this.$wire.getGroupedSelectableTableRecordKeys(e);this.areRecordsSelected(this.getRecordsInGroupOnPage(e))?this.deselectRecords(t):this.selectRecords(t),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let l=s.indexOf(this.lastChecked),r=s.indexOf(t),o=[l,r].sort((c,n)=>c-n),i=[];for(let c=o[0];c<=o[1];c++)s[c].checked=t.checked,i.push(s[c].value);t.checked?this.selectRecords(i):this.deselectRecords(i)}this.lastChecked=t}}}export{d as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,12 +3,20 @@
<div class="bg-white p-6 rounded-lg shadow-lg text-center flex flex-col items-center"> <div class="bg-white p-6 rounded-lg shadow-lg text-center flex flex-col items-center">
<div class="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12 mb-4"></div> <div class="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12 mb-4"></div>
<p class="text-gray-700 text-lg">{{ __('loading_spinner.processing_image') }}</p> <p class="text-gray-700 text-lg">{{ __('loading_spinner.processing_image') }}</p>
<p v-if="progress > 0" class="text-gray-700 text-sm mt-2">{{ progress }}%</p>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
// No script logic needed for a simple spinner import { defineProps } from 'vue';
const props = defineProps({
progress: {
type: Number,
default: 0,
},
});
</script> </script>
<style scoped> <style scoped>

View File

@@ -38,7 +38,7 @@
@delete="deleteStyledImage" @delete="deleteStyledImage"
/> />
<LoadingSpinner v-if="isLoading" /> <LoadingSpinner v-if="isLoading" :progress="processingProgress" />
</div> </div>
</template> </template>
@@ -79,6 +79,8 @@ const currentOverlayComponent = ref(null); // null, 'contextMenu', 'styleSelecto
const contextMenuPosition = ref({ x: 0, y: 0 }); const contextMenuPosition = ref({ x: 0, y: 0 });
const selectedImage = ref(null); const selectedImage = ref(null);
const styledImage = ref(null); // To store the newly styled image const styledImage = ref(null); // To store the newly styled image
const processingImageUuid = ref(null); // To store the UUID of the image being processed
const processingProgress = ref(0); // To store the progress percentage
const errorMessage = ref(null); // New ref for error messages const errorMessage = ref(null); // New ref for error messages
const isLoading = ref(false); // New ref for loading state const isLoading = ref(false); // New ref for loading state
const currentTheme = ref('light'); // New ref for current theme const currentTheme = ref('light'); // New ref for current theme
@@ -138,23 +140,91 @@ const applyStyle = (style, imageId) => {
console.log('Applying style:', style.title, 'to image:', imageId); console.log('Applying style:', style.title, 'to image:', imageId);
currentOverlayComponent.value = null; // Close style selector immediately currentOverlayComponent.value = null; // Close style selector immediately
isLoading.value = true; // Show loading spinner isLoading.value = true; // Show loading spinner
processingImageUuid.value = selectedImage.value.uuid; // Set the UUID of the image being processed
processingProgress.value = 0; // Reset progress
axios.post('/api/images/style-change', { axios.get('/api/comfyui-url')
image_id: imageId, .then(response => {
style_id: style.id, const comfyUiBaseUrl = response.data.comfyui_url;
}) const wsUrl = `ws://${new URL(comfyUiBaseUrl).host}/ws`;
.then(response => { const ws = new WebSocket(wsUrl);
console.log('Style change request successful:', response.data);
styledImage.value = response.data.styled_image; ws.onopen = () => {
currentOverlayComponent.value = 'styledImageDisplay'; console.log('WebSocket connected to ComfyUI.');
}) // Send prompt to ComfyUI via HTTP, then listen for progress via WS
.catch(error => { axios.post('/api/images/style-change', {
console.error('Error applying style:', error); image_id: imageId,
showError(error.response?.data?.error || 'Failed to apply style.'); style_id: style.id,
}) })
.finally(() => { .then(response => {
isLoading.value = false; // Hide loading spinner console.log('Style change request sent:', response.data);
}); // Store the prompt_id from the backend response
const promptId = response.data.prompt_id;
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'progress') {
console.log('ComfyUI Progress Message:', message);
const { value, max } = message.data;
const progress = (max > 0) ? (value / max) * 100 : 0;
if (message.data.prompt_id === promptId) {
processingProgress.value = progress;
if (processingProgress.value >= 100) {
console.log('Frontend: Progress reached 100%. Attempting to fetch final image.', { promptId: promptId });
// Fetch the final styled image from the backend
axios.get(`/api/images/fetch-styled/${promptId}`)
.then(imageResponse => {
console.log('Frontend: Successfully fetched styled image.', imageResponse.data);
styledImage.value = imageResponse.data.styled_image;
currentOverlayComponent.value = 'styledImageDisplay';
fetchImages(); // Refresh gallery
})
.catch(imageError => {
console.error('Frontend: Error fetching styled image:', imageError.response?.data?.error || imageError.message);
showError(imageError.response?.data?.error || 'Failed to fetch styled image.');
})
.finally(() => {
console.log('Frontend: Final fetch process completed.');
isLoading.value = false;
processingImageUuid.value = null;
processingProgress.value = 0;
ws.close();
});
}
}
} else {
console.warn('Received unexpected WebSocket message type:', message.type, message);
}
};
})
.catch(error => {
console.error('Error applying style:', error);
showError(error.response?.data?.error || 'Failed to apply style.');
isLoading.value = false;
processingImageUuid.value = null;
processingProgress.value = 0;
ws.close();
});
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
showError('WebSocket connection error.');
isLoading.value = false;
processingImageUuid.value = null;
processingProgress.value = 0;
};
ws.onclose = () => {
console.log('WebSocket closed.');
};
})
.catch(error => {
console.error('Error fetching ComfyUI URL:', error);
showError(error.response?.data?.error || 'Failed to get ComfyUI URL.');
isLoading.value = false;
});
}; };
const keepStyledImage = (imageToKeep) => { const keepStyledImage = (imageToKeep) => {

View File

@@ -9,6 +9,12 @@ window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
/** /**
* Echo exposes an expressive API for subscribing to channels and listening * Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting * for events that are broadcast by Laravel. Echo and event broadcasting

View File

@@ -15,4 +15,8 @@ return [
'navigation.next' => 'Weiter', 'navigation.next' => 'Weiter',
'navigation.page_of' => 'Seite :currentPage von :totalPages', 'navigation.page_of' => 'Seite :currentPage von :totalPages',
'loading_spinner.processing_image' => 'Bild wird verarbeitet...', 'loading_spinner.processing_image' => 'Bild wird verarbeitet...',
'styled_image_display.title' => 'Neu gestyltes Bild',
'styled_image_display.keep_button' => 'Behalten',
'styled_image_display.delete_button' => 'Löschen',
]; ];

View File

@@ -104,11 +104,6 @@ return [
'delete' => 'Löschen', 'delete' => 'Löschen',
], ],
], ],
'styled_image_display' => [
'title' => 'Neu gestyltes Bild',
'keep_button' => 'Behalten',
'delete_button' => 'Löschen',
],
'user' => [ 'user' => [
'navigation' => [ 'navigation' => [
'group' => 'Benutzerverwaltung', 'group' => 'Benutzerverwaltung',

View File

@@ -15,4 +15,7 @@ return [
'navigation.next' => 'Next', 'navigation.next' => 'Next',
'navigation.page_of' => 'Page :currentPage of :totalPages', 'navigation.page_of' => 'Page :currentPage of :totalPages',
'loading_spinner.processing_image' => 'Processing image...', 'loading_spinner.processing_image' => 'Processing image...',
'styled_image_display.title' => 'Newly Styled Image',
'styled_image_display.keep_button' => 'Keep',
'styled_image_display.delete_button' => 'Delete',
]; ];

View File

@@ -103,11 +103,6 @@ return [
], ],
], ],
], ],
'styled_image_display' => [
'title' => 'Newly Styled Image',
'keep_button' => 'Keep',
'delete_button' => 'Delete',
],
'loading_spinner' => [ 'loading_spinner' => [
'processing_image' => 'Processing image...', 'processing_image' => 'Processing image...',
], ],

View File

@@ -28,10 +28,12 @@ Route::post('/admin/navigation-state', [NavigationStateController::class, 'store
Route::get('/images', [ImageController::class, 'index']); Route::get('/images', [ImageController::class, 'index']);
Route::get('/styles', [StyleController::class, 'index']); Route::get('/styles', [StyleController::class, 'index']);
Route::post('/images/style-change', [ImageController::class, 'styleChangeRequest']); Route::post('/images/style-change', [ImageController::class, 'styleChangeRequest']);
Route::get('/comfyui-url', [ImageController::class, 'getComfyUiUrl']);
Route::middleware('auth:sanctum')->group(function () { Route::middleware('auth:sanctum')->group(function () {
Route::post('/images/keep', [ImageController::class, 'keepImage']); Route::post('/images/keep', [ImageController::class, 'keepImage']);
Route::delete('/images/{image}', [ImageController::class, 'deleteImage']); Route::delete('/images/{image}', [ImageController::class, 'deleteImage']);
Route::get('/images/status', [ImageController::class, 'getStatus']); Route::get('/images/status', [ImageController::class, 'getStatus']);
Route::get('/images/progress', [ImageController::class, 'getProgress']);
}); });
Route::get('/images/fetch-styled/{prompt_id}', [ImageController::class, 'fetchStyledImage']);