├── resources ├── dist │ ├── .gitkeep │ ├── backend.css │ └── backend.js ├── views │ ├── .gitkeep │ ├── components │ │ └── blank-field-wrapper.blade.php │ ├── modals │ │ └── generate-an-image.blade.php │ ├── forms │ │ └── components │ │ │ └── image-generator.blade.php │ ├── partials │ │ └── svg-defs.blade.php │ └── livewire │ │ └── generate-form.blade.php ├── js │ └── index.js ├── css │ └── index.css └── lang │ ├── pl │ └── backend.php │ ├── en │ └── backend.php │ ├── es │ └── backend.php │ └── de │ └── backend.php ├── src ├── FilamentImageGeneratorField.php ├── Facades │ └── FilamentImageGenerator.php ├── Services │ └── DownloadImageFromUrl.php ├── Contracts │ └── AIImageGenerator.php ├── Generators │ ├── OpenAIDallE2.php │ └── OpenAIDallE3.php ├── Forms │ └── Components │ │ └── ImageGenerator.php ├── FilamentImageGeneratorFieldServiceProvider.php └── Components │ └── GenerateForm.php ├── CHANGELOG.md ├── postcss.config.cjs ├── LICENSE.md ├── config └── filament-image-generator-field.php ├── bin └── build.js ├── composer.json └── README.md /resources/dist/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/js/index.js: -------------------------------------------------------------------------------- 1 | import fslightbox from 'fslightbox'; 2 | -------------------------------------------------------------------------------- /resources/views/components/blank-field-wrapper.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/modals/generate-an-image.blade.php: -------------------------------------------------------------------------------- 1 | @livewire('filament-image-generator-field::generate-form') 2 | -------------------------------------------------------------------------------- /src/FilamentImageGeneratorField.php: -------------------------------------------------------------------------------- 1 | toString() . '.' . $extension; 16 | 17 | Storage::disk($disk)->put($filename, $response->body()); 18 | 19 | return $filename; 20 | } 21 | 22 | public function saveToDisk(string $responseBody, string $disk, string $extension = 'jpg'): string 23 | { 24 | $filename = Str::uuid()->toString() . '.' . $extension; 25 | 26 | Storage::disk($disk)->put($filename, $responseBody); 27 | 28 | return $filename; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Contracts/AIImageGenerator.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/filament-image-generator-field.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'openai-dall-e-2' => \NaturalGroove\Filament\ImageGeneratorField\Generators\OpenAIDallE2::class, 8 | 'openai-dall-e-3' => \NaturalGroove\Filament\ImageGeneratorField\Generators\OpenAIDallE3::class, 9 | ], 10 | 11 | // Define the default generator to use 12 | 'default-generator' => 'openai-dall-e-3', 13 | 14 | // Define the configuration for each generator 15 | // The key is the name of the generator and the value is an array of configuration options 16 | // The configuration options are specific to each generator 17 | // For example, the OpenAI DALL-E generator requires an API key 18 | 'openai-dall-e' => [ 19 | 'api_key' => env('OPEN_AI_DALL_E_API_KEY', null), 20 | 'output-format' => 'png', 21 | ], 22 | 23 | // Define modals configuration options 24 | 'modal' => [ 25 | 'generate-form' => [ 26 | 'width' => '6xl', 27 | ] 28 | ] 29 | ]; 30 | -------------------------------------------------------------------------------- /bin/build.js: -------------------------------------------------------------------------------- 1 | import esbuild from 'esbuild' 2 | 3 | const isDev = process.argv.includes('--dev') 4 | 5 | async function compile(options) { 6 | const context = await esbuild.context(options) 7 | 8 | if (isDev) { 9 | await context.watch() 10 | } else { 11 | await context.rebuild() 12 | await context.dispose() 13 | } 14 | } 15 | 16 | const defaultOptions = { 17 | define: { 18 | 'process.env.NODE_ENV': isDev ? `'development'` : `'production'`, 19 | }, 20 | bundle: true, 21 | mainFields: ['module', 'main'], 22 | platform: 'neutral', 23 | sourcemap: isDev ? 'inline' : false, 24 | sourcesContent: isDev, 25 | treeShaking: true, 26 | target: ['es2020'], 27 | minify: !isDev, 28 | plugins: [{ 29 | name: 'watchPlugin', 30 | setup: function (build) { 31 | build.onStart(() => { 32 | console.log(`Build started at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`) 33 | }) 34 | 35 | build.onEnd((result) => { 36 | if (result.errors.length > 0) { 37 | console.log(`Build failed at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`, result.errors) 38 | } else { 39 | console.log(`Build finished at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`) 40 | } 41 | }) 42 | } 43 | }], 44 | } 45 | 46 | compile({ 47 | ...defaultOptions, 48 | entryPoints: ['./resources/js/index.js'], 49 | outfile: './resources/dist/backend.js', 50 | }) 51 | -------------------------------------------------------------------------------- /resources/lang/pl/backend.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'generate-using-ai' => 'Generuj za pomocą AI', 8 | 'edit-using-ai' => 'Edytuj za pomocą AI', 9 | ], 10 | 11 | 'form' => [ 12 | 'fields' => [ 13 | 'showOptions' => 'Pokaż opcje', 14 | 'options' => 'Opcje', 15 | 16 | 'prompt' => 'Polecenie', 17 | 'prompt-placeholder' => 'na przykład: `Kot siedzący na kanapie`. Staraj się być jak najbardziej opisowy.', 18 | 19 | 'n' => 'Liczba generowanych obrazów', 20 | 'aspect_ratio' => 'Proporcje', 21 | 'size' => 'Rozmiar', 22 | 'size-hint' => 'Wybierz rozmiar obrazu.', 23 | 'style' => 'Styl', 24 | 'style-hint' => 'Wybierz styl obrazu.', 25 | 'quality' => 'Jakość', 26 | 'quality-hint' => 'Wybierz jakość obrazu.', 27 | ], 28 | 29 | 'errors' => [ 30 | 'no-images-generated' => 'Nie wygenerowano żadnych obrazów. Spróbuj ponownie z innym poleceniem.', 31 | ] 32 | ], 33 | 34 | 'modals' => [ 35 | 'generate-an-image' => [ 36 | 'title' => 'Generuj obraz', 37 | 'description' => 'Opisz obraz, który chcesz wygenerować.
Poczekaj kilka sekund, aż obraz będzie gotowy.', 38 | 'generate' => 'Generuj', 39 | 'generating' => 'Generowanie...', 40 | 'add-generated' => 'Dodaj wygenerowany obraz', 41 | 'cancel' => 'Anuluj', 42 | 'select' => 'Wybierz', 43 | 'uploading' => 'Przesyłanie...', 44 | 45 | 'configuration-error' => 'Konfiguracja dla wybranego generatora jest nieprawidłowa. Sprawdź konfigurację, taką jak klucz API, i spróbuj ponownie. ', 46 | ] 47 | ] 48 | ]; 49 | -------------------------------------------------------------------------------- /resources/lang/en/backend.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'generate-using-ai' => 'Generate using AI', 8 | 'edit-using-ai' => 'Edit using AI', 9 | ], 10 | 11 | 'form' => [ 12 | 'fields' => [ 13 | 'showOptions' => 'Show Options', 14 | 'options' => 'Options', 15 | 16 | 'prompt' => 'Prompt', 17 | 'prompt-placeholder' => 'for example: `A cat sitting on a couch`. Try to be as descriptive as possible.', 18 | 19 | 'n' => 'Number of Images to Generate', 20 | 'aspect_ratio' => 'Aspect ratio', 21 | 'size' => 'Size', 22 | 'size-hint' => 'Select the size of the image.', 23 | 'style' => 'Style', 24 | 'style-hint' => 'Select the style of the image.', 25 | 'quality' => 'Quality', 26 | 'quality-hint' => 'Select the quality of the image.', 27 | ], 28 | 29 | 'errors' => [ 30 | 'no-images-generated' => 'No images were generated. Please try again with a different prompt.', 31 | ] 32 | ], 33 | 34 | 'modals' => [ 35 | 'generate-an-image' => [ 36 | 'title' => 'Generate an Image', 37 | 'description' => 'Describe the image You want to generate.
Wait for a few seconds until your image is ready.', 38 | 'generate' => 'Generate', 39 | 'generating' => 'Generating...', 40 | 'add-generated' => 'Add generated image', 41 | 'cancel' => 'Cancel', 42 | 'select' => 'Select', 43 | 'uploading' => 'Uploading...', 44 | 45 | 'configuration-error' => 'Configuration for the selected generator is invalid. Please check the configuration like API key and try again. ', 46 | ] 47 | ] 48 | ]; 49 | -------------------------------------------------------------------------------- /resources/lang/es/backend.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'generate-using-ai' => 'Generar usando IA', 8 | 'edit-using-ai' => 'Editar usando IA', 9 | ], 10 | 11 | 'form' => [ 12 | 'fields' => [ 13 | 'showOptions' => 'Mostrar opciones', 14 | 'options' => 'Opciones', 15 | 16 | 'prompt' => 'Indicación', 17 | 'prompt-placeholder' => 'por ejemplo: `Un gato sentado en un sofá`. Intenta ser lo más descriptivo posible.', 18 | 19 | 'n' => 'Número de imágenes a generar', 20 | 'aspect_ratio' => 'Relación de aspecto', 21 | 'size' => 'Tamaño', 22 | 'size-hint' => 'Selecciona el tamaño de la imagen.', 23 | 'style' => 'Estilo', 24 | 'style-hint' => 'Selecciona el estilo de la imagen.', 25 | 'quality' => 'Calidad', 26 | 'quality-hint' => 'Selecciona la calidad de la imagen.', 27 | ], 28 | 29 | 'errors' => [ 30 | 'no-images-generated' => 'No se generaron imágenes. Por favor, intenta de nuevo con una indicación diferente.', 31 | ] 32 | ], 33 | 34 | 'modals' => [ 35 | 'generate-an-image' => [ 36 | 'title' => 'Generar una imagen', 37 | 'description' => 'Describe la imagen que quieres generar.
Espera unos segundos hasta que tu imagen esté lista.', 38 | 'generate' => 'Generar', 39 | 'generating' => 'Generando...', 40 | 'add-generated' => 'Agregar imagen generada', 41 | 'cancel' => 'Cancelar', 42 | 'select' => 'Seleccionar', 43 | 'uploading' => 'Subiendo...', 44 | 45 | 'configuration-error' => 'La configuración para el generador seleccionado no es válida. Por favor, verifica la configuración como la clave de API e intenta nuevamente. ', 46 | ] 47 | ] 48 | ]; 49 | -------------------------------------------------------------------------------- /resources/lang/de/backend.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'generate-using-ai' => 'Mit AI generieren', 8 | 'edit-using-ai' => 'Mit AI bearbeiten', 9 | ], 10 | 11 | 'form' => [ 12 | 'fields' => [ 13 | 'showOptions' => 'Optionen anzeigen', 14 | 'options' => 'Optionen', 15 | 16 | 'prompt' => 'Aufforderung', 17 | 'prompt-placeholder' => 'zum Beispiel: `Eine Katze sitzt auf einem Sofa`. Versuche so genau wie möglich zu sein.', 18 | 19 | 'n' => 'Anzahl der zu generierenden Bilder', 20 | 'aspect_ratio' => 'Seitenverhältnis', 21 | 'size' => 'Größe', 22 | 'size-hint' => 'Wähle die Größe des Bildes aus.', 23 | 'style' => 'Stil', 24 | 'style-hint' => 'Wähle den Stil des Bildes aus.', 25 | 'quality' => 'Qualität', 26 | 'quality-hint' => 'Wähle die Qualität des Bildes aus.', 27 | ], 28 | 29 | 'errors' => [ 30 | 'no-images-generated' => 'Es wurden keine Bilder generiert. Bitte versuche es erneut mit einer anderen Aufforderung.', 31 | ] 32 | ], 33 | 34 | 'modals' => [ 35 | 'generate-an-image' => [ 36 | 'title' => 'Bild generieren', 37 | 'description' => 'Beschreibe das Bild, das du generieren möchtest.
Warte einige Sekunden, bis dein Bild fertig ist.', 38 | 'generate' => 'Generieren', 39 | 'generating' => 'Generiere...', 40 | 'add-generated' => 'Generiertes Bild hinzufügen', 41 | 'cancel' => 'Abbrechen', 42 | 'select' => 'Auswählen', 43 | 'uploading' => 'Hochladen...', 44 | 45 | 'configuration-error' => 'Die Konfiguration für den ausgewählten Generator ist ungültig. Bitte überprüfen Sie die Konfiguration, wie z.B. den API-Schlüssel, und versuchen Sie es erneut.' 46 | ] 47 | ] 48 | ]; 49 | -------------------------------------------------------------------------------- /resources/views/forms/components/image-generator.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
14 |
15 | @include('filament-forms::components.file-upload') 16 |
17 | 18 |
19 | 23 |
24 | 25 | 26 | 27 | 28 | {{ Str::ucfirst(__('filament-image-generator-field::backend.labels.generate-using-ai')) }} 29 |
30 |
31 | 32 | 39 |
40 | 41 | 42 | 43 | 44 | {{ Str::ucfirst(__('filament-image-generator-field::backend.labels.edit-using-ai')) }} 45 |
46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/Generators/OpenAIDallE2.php: -------------------------------------------------------------------------------- 1 | getOpenAIClient(); 17 | 18 | $response = $client->images()->create([ 19 | 'model' => 'dall-e-2', 20 | 'prompt' => $prompt, 21 | 'n' => $n, 22 | 'size' => '1024x1024', // '1024x1024' is the only supported aspect ratio for DALL-E 2 23 | 'response_format' => 'url', 24 | ]); 25 | 26 | //Log::info('OpenAI Dall-E response', $response->toArray()); 27 | 28 | return $response->toArray()['data']; 29 | } 30 | 31 | public function fetch(string $url): string 32 | { 33 | $response = Http::get($url); 34 | 35 | return $response->body(); 36 | } 37 | public function getName(): string 38 | { 39 | return static::$name; 40 | } 41 | 42 | public function getFileExtension(): string 43 | { 44 | return config('filament-image-generator-field.openai-dall-e.file_extension') ?? 'png'; 45 | } 46 | 47 | public function getSupportedOptions(): array 48 | { 49 | return [ 50 | 'n' => [ 51 | '1' => '1', 52 | '2' => '2', 53 | '4' => '4', 54 | ], 55 | 56 | 'aspect_ratio' => [ 57 | '1:1' => '1:1', 58 | ] 59 | ]; 60 | } 61 | 62 | public function validateConfiguration(): bool 63 | { 64 | return config('filament-image-generator-field.openai-dall-e.api_key') !== null; 65 | } 66 | 67 | // ************************************************************ 68 | 69 | protected function getOpenAIClient(): OpenAI\Client 70 | { 71 | return OpenAI::client(config('filament-image-generator-field.openai-dall-e.api_key')); 72 | } 73 | 74 | protected function matchAspectRatio(string $aspectRatio): string 75 | { 76 | $aspectRatios = [ 77 | '1:1' => '1024x1024', 78 | ]; 79 | 80 | return $aspectRatios[$aspectRatio] ?? '1024x1024'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Forms/Components/ImageGenerator.php: -------------------------------------------------------------------------------- 1 | image(); 24 | $this->columnSpanFull(); 25 | 26 | // register the modal view with component - prevent multiple registration 27 | if (!static::$isComponentRegistered) { 28 | FilamentView::registerRenderHook( 29 | PanelsRenderHook::CONTENT_END, 30 | fn (): View => view('filament-image-generator-field::modals.generate-an-image'), 31 | ); 32 | 33 | static::$isComponentRegistered = true; 34 | } 35 | 36 | $this->imageGenerator(config('filament-image-generator-field.default-generator')); 37 | } 38 | 39 | public function getFieldWrapperView(?string $scope = null): string 40 | { 41 | if ($scope === 'generator') { 42 | return $this->getCustomFieldWrapperView() ?? 43 | $this->getContainer()->getCustomFieldWrapperView() ?? 44 | 'filament-forms::field-wrapper'; 45 | } 46 | 47 | return 'filament-image-generator-field::blank-field-wrapper'; 48 | } 49 | 50 | public function imageGenerator(string | Closure | null $imageGenerator): static 51 | { 52 | if (is_string($imageGenerator) && !config("filament-image-generator-field.generators.{$imageGenerator}")) { 53 | throw new \InvalidArgumentException(__('Image generator `{$imageGenerator}` is not defined in the configuration..')); 54 | } 55 | 56 | $this->imageGenerator = $imageGenerator; 57 | 58 | return $this; 59 | } 60 | 61 | public function getImageGenerator(): ?string 62 | { 63 | return $this->evaluate($this->imageGenerator); 64 | } 65 | 66 | public function useDallE2(): static 67 | { 68 | return $this->imageGenerator('openai-dall-e-2'); 69 | } 70 | 71 | public function useDallE3(): static 72 | { 73 | return $this->imageGenerator('openai-dall-e-3'); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /resources/views/partials/svg-defs.blade.php: -------------------------------------------------------------------------------- 1 | {{-- viewBox="0 0 380 379" --}} 2 | 28 | -------------------------------------------------------------------------------- /src/Generators/OpenAIDallE3.php: -------------------------------------------------------------------------------- 1 | getOpenAIClient(); 17 | 18 | $response = $client->images()->create([ 19 | 'model' => 'dall-e-3', 20 | 'prompt' => $prompt, 21 | 'n' => 1, 22 | 'size' => $this->matchAspectRatio($params['aspect_ratio']), 23 | 'response_format' => 'url', 24 | 'quality' => $params['quality'] ?? 'standard', 25 | 'style' => $params['style'] ?? 'natural', 26 | ]); 27 | 28 | //Log::info('OpenAI Dall-E response', $response->toArray()); 29 | 30 | return $response->toArray()['data']; 31 | } 32 | 33 | public function fetch(string $url): string 34 | { 35 | $response = Http::get($url); 36 | 37 | return $response->body(); 38 | } 39 | 40 | public function getName(): string 41 | { 42 | return static::$name; 43 | } 44 | 45 | public function getFileExtension(): string 46 | { 47 | return config('filament-image-generator-field.openai-dall-e.file_extension') ?? 'png'; 48 | } 49 | 50 | public function getSupportedOptions(): array 51 | { 52 | return [ 53 | 'aspect_ratio' => [ 54 | '1:1' => '1:1', 55 | '16:9' => '16:9', 56 | '9:16' => '9:16', 57 | ], 58 | 59 | 'quality' => [ 60 | 'standard' => 'Standard', 61 | 'hd' => 'High definition', 62 | ], 63 | 64 | 'style' => [ 65 | 'natural' => 'Natural', 66 | 'vivid' => 'Vivid', 67 | ], 68 | ]; 69 | } 70 | 71 | public function validateConfiguration(): bool 72 | { 73 | return config('filament-image-generator-field.openai-dall-e.api_key') !== null; 74 | } 75 | 76 | // ************************************************************ 77 | 78 | protected function getOpenAIClient(): OpenAI\Client 79 | { 80 | return OpenAI::client(config('filament-image-generator-field.openai-dall-e.api_key')); 81 | } 82 | 83 | protected function matchAspectRatio(string $aspectRatio): string 84 | { 85 | $aspectRatios = [ 86 | '1:1' => '1024x1024', 87 | '16:9' => '1792x1024', 88 | '9:16' => '1024x1792', 89 | ]; 90 | 91 | return $aspectRatios[$aspectRatio] ?? '1024x1024'; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/FilamentImageGeneratorFieldServiceProvider.php: -------------------------------------------------------------------------------- 1 | name(static::$name) 28 | ->hasConfigFile() 29 | ->hasTranslations() 30 | ->hasViews(); 31 | 32 | $package->hasInstallCommand(function (InstallCommand $command) { 33 | $command 34 | ->startWith(function (InstallCommand $command) { 35 | $command->info('Hello, and welcome to Filament ImageGeneratorField installation!'); 36 | 37 | if ($command->confirm('Would you like to publish the config file?', false)) { 38 | $command->callSilent('vendor:publish', ['--tag' => 'filament-image-generator-field', '--force' => true]); 39 | } 40 | 41 | if ($command->confirm('Would you like to publish the translations?', false)) { 42 | $command->callSilent('vendor:publish', ['--tag' => 'filament-image-generator-field', '--force' => true]); 43 | } 44 | }) 45 | ->publishAssets() 46 | ->endWith(function (InstallCommand $command) { 47 | $command->info('Have a great day!'); 48 | }); 49 | }); 50 | 51 | Livewire::component('filament-image-generator-field::generate-form', GenerateForm::class); 52 | 53 | // Asset Registration 54 | FilamentAsset::register( 55 | $this->getAssets(), 56 | $this->getAssetPackageName() 57 | ); 58 | } 59 | 60 | /** 61 | * @return array<\Filament\Support\Assets\Asset>> 62 | */ 63 | protected function getAssets(): array 64 | { 65 | return [ 66 | Css::make('backend', __DIR__ . '/../resources/dist/backend.css')->loadedOnRequest(), 67 | Js::make('backend', __DIR__ . '/../resources/dist/backend.js')->loadedOnRequest() 68 | 69 | ]; 70 | } 71 | 72 | protected function getAssetPackageName(): string 73 | { 74 | return 'naturalgroove/filament-image-generator-field'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "naturalgroove/laravel-filament-image-generator-field", 3 | "description": "A Laravel Filament plugin that generates images using AI directly in Admin Panel", 4 | "keywords": [ 5 | "naturalGroove", 6 | "laravel", 7 | "laravel-filament-cms", 8 | "laravel-filament", 9 | "laravel-filament-plugin", 10 | "laravel-filament-image-generator-field", 11 | "ai-image-generator" 12 | ], 13 | "homepage": "https://github.com/naturalgroove/laravel-filament-image-generator-field", 14 | "support": { 15 | "issues": "https://github.com/naturalgroove/laravel-filament-image-generator-field/issues", 16 | "source": "https://github.com/naturalgroove/laravel-filament-image-generator-field" 17 | }, 18 | "license": "MIT", 19 | "authors": [ 20 | { 21 | "name": "Grzegorz Adamczyk", 22 | "email": "naturalgroove@gmail.com", 23 | "role": "Developer" 24 | } 25 | ], 26 | "require": { 27 | "php": "^8.1", 28 | "filament/forms": "^3.0", 29 | "guzzlehttp/guzzle": "^7.8", 30 | "openai-php/client": "^0.8.5|^0.9.0|^0.10.0", 31 | "spatie/laravel-package-tools": "^1.15.0", 32 | "symfony/http-client": "^6.4|^7.1.4" 33 | }, 34 | "require-dev": { 35 | "laravel/pint": "^1.0", 36 | "nunomaduro/collision": "^7.9", 37 | "larastan/larastan": "^2.0.1", 38 | "orchestra/testbench": "^8.0", 39 | "pestphp/pest": "^2.1", 40 | "pestphp/pest-plugin-arch": "^2.0", 41 | "pestphp/pest-plugin-laravel": "^2.0", 42 | "phpstan/extension-installer": "^1.1", 43 | "phpstan/phpstan-deprecation-rules": "^1.0", 44 | "phpstan/phpstan-phpunit": "^1.0" 45 | }, 46 | "autoload": { 47 | "psr-4": { 48 | "NaturalGroove\\Filament\\ImageGeneratorField\\": "src/" 49 | } 50 | }, 51 | "autoload-dev": { 52 | "psr-4": { 53 | "NaturalGroove\\Filament\\ImageGeneratorField\\Tests\\": "tests/" 54 | } 55 | }, 56 | "scripts": { 57 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 58 | "analyse": "vendor/bin/phpstan analyse", 59 | "test": "vendor/bin/pest", 60 | "test-coverage": "vendor/bin/pest --coverage", 61 | "format": "vendor/bin/pint" 62 | }, 63 | "config": { 64 | "sort-packages": true, 65 | "allow-plugins": { 66 | "pestphp/pest-plugin": true, 67 | "phpstan/extension-installer": true, 68 | "php-http/discovery": true 69 | } 70 | }, 71 | "extra": { 72 | "laravel": { 73 | "providers": [ 74 | "NaturalGroove\\Filament\\ImageGeneratorField\\FilamentImageGeneratorFieldServiceProvider" 75 | ], 76 | "aliases": { 77 | "FilamentImageGenerator": "NaturalGroove\\Filament\\ImageGeneratorField\\Facades\\ImageGeneratorField" 78 | } 79 | } 80 | }, 81 | "minimum-stability": "dev", 82 | "prefer-stable": true 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Generator Form Field for Laravel Filament 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/naturalGroove/laravel-filament-image-generator-field.svg?style=flat-square)](https://packagist.org/packages/naturalGroove/laravel-filament-image-generator-field) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/naturalGroove/laravel-filament-image-generator-field/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/naturalGroove/laravel-filament-image-generator-field/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/naturalGroove/laravel-filament-image-generator-field/fix-php-code-styling.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/naturalGroove/laravel-filament-image-generator-field/actions?query=workflow%3A"Fix+PHP+code+styling"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/naturalGroove/laravel-filament-image-generator-field.svg?style=flat-square)](https://packagist.org/packages/naturalGroove/laravel-filament-image-generator-field) 7 | 8 | This custom field allows you to generate images with different sizes and formats using AI models like OpenAI DALL-E. 9 | It extends the FileUpload field and adds a button to open the image generator modal where you can set the sizes and formats of the generated images. 10 | 11 | filament image generator ai 12 | 13 | # Installation 14 | 15 | Before you begin, you must have the Laravel Filament package installed and configured. If you haven't done this yet, you can find the installation instructions [here](https://filamentadmin.com/docs/installation). 16 | 17 | ## Prerequisites 18 | 19 | Default Image Generator is set to OpenAI DALL-E (version 3). You should have an API key to use it. You can get it [here](https://platform.openai.com/). 20 | After You get the API key, you should set it in your .env file: 21 | 22 | ```bash 23 | OPEN_AI_DALL_E_API_KEY=your-api-key 24 | ``` 25 | 26 | ## Install the package via composer 27 | 28 | Run the following command in your terminal to install the package: 29 | 30 | ```bash 31 | composer require naturalGroove/laravel-filament-image-generator-field 32 | ``` 33 | 34 | You can publish the config file with: 35 | 36 | ```bash 37 | php artisan vendor:publish --tag="filament-image-generator-field-config" 38 | ``` 39 | 40 | Configuration file lets you set the default image generator and the available image generators for the field. 41 | 42 | Optionally, you can publish the views to customize the field: 43 | 44 | ```bash 45 | php artisan vendor:publish --tag="filament-image-generator-field-views" 46 | ``` 47 | 48 | ## Usage 49 | 50 | ![screenshot](https://netseven.dev/filament-image-generator-field/screenshots/plugin-screencast.png) 51 | 52 | Just add new Field or replace your FileUpload field with ImageGenerator field in your form schema definition: 53 | 54 | ```php 55 | use \NaturalGroove\Filament\ImageGeneratorField\Forms\Components\ImageGenerator; 56 | 57 | [...] 58 | public static function form(Form $form): Form 59 | { 60 | return $form 61 | ->schema([ 62 | ImageGenerator::make('photo'), 63 | ]); 64 | ``` 65 | 66 | If You are replacing the FileUpload field: 67 | 68 | ```diff 69 | use \NaturalGroove\Filament\ImageGeneratorField\Forms\Components\ImageGenerator; 70 | 71 | [...] 72 | - FileUpload::make('photo'), 73 | + ImageGenerator::make('photo'), 74 | ``` 75 | 76 | You could use all the same options as FileUpload field, for example: 77 | 78 | ```php 79 | use \NaturalGroove\Filament\ImageGeneratorField\Forms\Components\ImageGenerator; 80 | 81 | ImageGenerator::make('photo') 82 | ->imageEditor() 83 | ->disk('private'), 84 | ``` 85 | 86 | This plugin comes with a default image generator set to OpenAI DALL-E. 87 | You can select which version of the model you want to use when defining the field: 88 | 89 | ```php 90 | ImageGenerator::make('photo' 91 | ->imageGenerator('openai-dall-e-3'), 92 | ``` 93 | 94 | There are predefined shortcuts for the image generators: 95 | 96 | ```php 97 | ImageGenerator::make('photo') 98 | ->openaiDallE2(); // equivalent to ->imageGenerator('openai-dall-e-2') 99 | 100 | ImageGenerator::make('photo') 101 | ->openaiDallE3(); // equivalent to ->imageGenerator('openai-dall-e-3') 102 | ``` 103 | 104 | Depending on the image generator you choose, there are different options you can set. For example Dall-E 2 allows to set the number of images generated. 105 | 106 | After you add the field to your form, you should see a button next to the file input. When you click the button, the image generator modal will open. 107 | 108 | ![screenshot](https://netseven.dev/filament-image-generator-field/screenshots/field-with-button.webp) 109 | 110 | ## Adding custom image generators 111 | 112 | You can add custom image generators by impolemeting the `NaturalGroove\Filament\ImageGeneratorField\Contracts\AIImageGenerator` interface. 113 | Your class should have the all the methods from the interface and should be registered in the config file. 114 | Format of returned array should be the same as in the example below: 115 | 116 | ```php 117 | use NaturalGroove\Filament\ImageGeneratorField\Contracts\AIImageGenerator; 118 | 119 | class MyCustomImageGenerator implements AIImageGenerator 120 | { 121 | public function generate(string $prompt, int $n = 1, array $params = []): array 122 | { 123 | // your implementation 124 | 125 | return [ 126 | [ 127 | 'url' => 'https://example.com/image.jpg' 128 | ] 129 | [...] 130 | ]; 131 | } 132 | 133 | } 134 | ``` 135 | 136 | ![screenshot](https://netseven.dev/filament-image-generator-field/screenshots/modal-with-show-all-options.webp) 137 | 138 | ## Upcoming features 139 | 140 | - [ ] Add more image generators 141 | - [ ] Add functionality to edit your uploaded image with AI models (img2img) 142 | - [ ] Add more options to the field 143 | 144 | ## Changelog 145 | 146 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 147 | 148 | ## Contributing 149 | 150 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 151 | 152 | ## Security Vulnerabilities 153 | 154 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 155 | 156 | ## Credits 157 | 158 | - [Grzegorz Adamczyk](https://github.com/naturalGroove) 159 | - [All Contributors](../../contributors) 160 | 161 | ## License 162 | 163 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 164 | -------------------------------------------------------------------------------- /resources/views/livewire/generate-form.blade.php: -------------------------------------------------------------------------------- 1 |
48 | 55 | 56 | {{ __('filament-image-generator-field::backend.modals.generate-an-image.title') }} 57 | ({{ $generatorName }}) 58 | 59 | 60 |
61 | @if ($isConfigurationOk) 62 |
63 |
64 |
65 |
66 | 67 | 68 | 69 |
70 | 71 |
74 | @if (count($generatedImages) === 1) 75 | 81 | 82 | 83 | @elseif (count($generatedImages) > 1) 84 | @foreach ($generatedImages as $key => $generatedImage) 85 |
86 | 87 | 88 | 89 | 90 | 91 | @lang('filament-image-generator-field::backend.modals.generate-an-image.select') 92 | 93 |
94 | @endforeach 95 | @else 96 | @endif 97 |
98 |
99 |
100 | 101 |
102 | 109 | 110 | {{ $this->promptForm }} 111 | 112 |
113 | 114 |
115 | 116 | 117 | 118 | 119 | 120 | 121 | {{ Str::ucfirst(__('filament-image-generator-field::backend.modals.generate-an-image.generate')) }} 122 | {{ Str::ucfirst(__('filament-image-generator-field::backend.modals.generate-an-image.generating')) }} 123 |
124 |
125 |
126 |
127 |
128 | @else 129 | 136 | @endif 137 |
138 | 139 | 140 |
141 |
142 | 143 | {{ Str::ucfirst(__('filament-image-generator-field::backend.modals.generate-an-image.cancel')) }} 144 | 145 | 146 | 152 |
153 | 154 | 155 | 158 | 159 |
160 |
161 |
162 |
163 |
164 |
165 | 166 | @include('filament-image-generator-field::partials.svg-defs') 167 | 168 | 180 |
181 | -------------------------------------------------------------------------------- /src/Components/GenerateForm.php: -------------------------------------------------------------------------------- 1 | isConfigurationOk = $this->getGeneratorObject(config('filament-image-generator-field.default-generator'))->validateConfiguration(); 45 | 46 | $this->updateImageGenerator(config('filament-image-generator-field.default-generator')); 47 | } 48 | 49 | public function render(): View 50 | { 51 | return view('filament-image-generator-field::livewire.generate-form'); 52 | } 53 | 54 | protected function getForms(): array 55 | { 56 | return [ 57 | 'promptForm' => $this->makeForm() 58 | ->columns(2) 59 | ->schema([ 60 | Textarea::make('prompt') 61 | ->translateLabel() 62 | ->label('filament-image-generator-field::backend.form.fields.prompt') 63 | ->columnSpan(2) 64 | ->placeholder(__('filament-image-generator-field::backend.form.fields.prompt-placeholder')) 65 | ->rows(5) 66 | ->rules(['string', 'min:10']) 67 | ->default($this->prompt) 68 | ->required(), 69 | Checkbox::make('showOptions') 70 | ->translateLabel() 71 | ->label('filament-image-generator-field::backend.form.fields.showOptions') 72 | ->default(false) 73 | ->live(), 74 | Fieldset::make(__('filament-image-generator-field::backend.form.fields.options')) 75 | ->visible(fn (Get $get): bool => $get('showOptions')) 76 | ->schema([ 77 | Select::make('n') 78 | ->translateLabel() 79 | ->label('filament-image-generator-field::backend.form.fields.n') 80 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['n'] ?? [1 => 1]) 81 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['n'])) 82 | ->rules(['integer']) 83 | ->default(1), 84 | Select::make('aspect_ratio') 85 | ->translateLabel() 86 | ->label('filament-image-generator-field::backend.form.fields.aspect_ratio') 87 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['aspect_ratio'] ?? []) 88 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['aspect_ratio'])) 89 | ->rules(['string']) 90 | ->required(), 91 | Select::make('size') 92 | ->translateLabel() 93 | ->label('filament-image-generator-field::backend.form.fields.size') 94 | ->hint(__('filament-image-generator-field::backend.form.fields.size-hint')) 95 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['size'] ?? []) 96 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['size'])) 97 | ->rules(['string']) 98 | ->required(), 99 | Select::make('style') 100 | ->translateLabel() 101 | ->label('filament-image-generator-field::backend.form.fields.style') 102 | ->hint(__('filament-image-generator-field::backend.form.fields.style-hint')) 103 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['style'] ?? []) 104 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['style'])) 105 | ->rules(['string']) 106 | ->required(), 107 | Select::make('quality') 108 | ->translateLabel() 109 | ->label('filament-image-generator-field::backend.form.fields.quality') 110 | ->hint(__('filament-image-generator-field::backend.form.fields.quality-hint')) 111 | ->options(fn () => $this->getGeneratorObject($this->generatorName)->getSupportedOptions()['quality'] ?? []) 112 | ->visible(fn (): bool => isset($this->getGeneratorObject($this->generatorName)->getSupportedOptions()['quality'])) 113 | ->rules(['string']) 114 | ->required(), 115 | ]) 116 | ->columns(1) 117 | ]) 118 | ]; 119 | } 120 | 121 | public function generateImage(string $generator): void 122 | { 123 | // empty the generated images array 124 | $this->generatedImages = []; 125 | $this->url = null; 126 | 127 | $this->validate(); 128 | 129 | $this->verifyGenerator($generator); 130 | 131 | try { 132 | $this->generator = $this->getGeneratorObject($generator); 133 | 134 | $this->generatedImages = $this->generator->generate($this->prompt ?? '', $this->n, [ 135 | 'aspect_ratio' => $this->aspect_ratio ?? null, 136 | 'size' => $this->size ?? null, 137 | 'quality' => $this->quality ?? null, 138 | 'style' => $this->style ?? null, 139 | ]); 140 | 141 | $this->processGeneratedImages(); 142 | } catch (\Exception $e) { 143 | $this->addError('prompt', $e->getMessage()); 144 | } 145 | } 146 | 147 | public function selectImage(int $index): void 148 | { 149 | $this->url = $this->generatedImages[$index]['url']; 150 | } 151 | 152 | #[On('update-image-generator')] 153 | public function updateImageGenerator(string $generator): void 154 | { 155 | if ($this->generatorName !== $generator) { 156 | $this->verifyGenerator($generator); 157 | 158 | $this->generatorName = $generator; 159 | $this->generator = $this->getGeneratorObject($this->generatorName); 160 | 161 | // check if the configuration for the generator is ok 162 | $this->isConfigurationOk = $this->getGeneratorObject(config('filament-image-generator-field.default-generator'))->validateConfiguration(); 163 | 164 | // set the default values for the form fields 165 | $defaultFields = []; 166 | 167 | // set the default values for the form fields 168 | foreach ($this->generator->getSupportedOptions() as $key => $options) { 169 | $this->{$key} = array_key_first($options); 170 | $defaultFields[$key] = $this->{$key}; 171 | } 172 | 173 | $this->showOptions = false; 174 | 175 | $this->getForm('promptForm')?->fill( 176 | array_merge( 177 | $defaultFields, 178 | [ 179 | 'n' => $this->n 180 | ] 181 | ) 182 | ); 183 | } 184 | } 185 | 186 | #[On('add-selected')] 187 | public function addSelected(array $image, string $statePath, string $disk, string $generator): void 188 | { 189 | $this->generator = $this->getGeneratorObject($generator); 190 | 191 | $localFileName = (new DownloadImageFromUrl())->saveToDisk($this->generator->fetch($image['url']), $disk, $this->generator->getFileExtension()); 192 | 193 | $this->dispatch('generated-image-uploaded', uuid: Str::uuid()->toString(), localFileName: $localFileName, statePath: $statePath); 194 | } 195 | 196 | protected function verifyGenerator(string $generator): void 197 | { 198 | if (config("filament-image-generator-field.generators.{$generator}") === null) { 199 | throw new \InvalidArgumentException("Image generator `{$generator}` is not defined in the configuration."); 200 | } 201 | } 202 | 203 | protected function getGeneratorObject(?string $generator): AIImageGenerator 204 | { 205 | if ($this->generator === null) { 206 | // @phpstan-ignore-next-line 207 | $this->generator = new (config("filament-image-generator-field.generators.{$generator}"))(); 208 | } 209 | 210 | // @phpstan-ignore-next-line 211 | return $this->generator; 212 | } 213 | 214 | protected function processGeneratedImages(): void 215 | { 216 | if (count($this->generatedImages) === 0) { 217 | $this->addError('prompt', __('filament-image-generator-field::backend.form.errors.no-images-generated')); 218 | } 219 | 220 | if (count($this->generatedImages) === 1) { 221 | $this->url = $this->generatedImages[0]['url']; 222 | } 223 | 224 | if (count($this->generatedImages) > 1) { 225 | // TODO: Implement multiple images - if needed 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /resources/dist/backend.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border-width:0;border-style:solid;border-color:rgba(var(--gray-200),1)}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:rgba(var(--gray-400),1)}input::placeholder,textarea::placeholder{opacity:1;color:rgba(var(--gray-400),1)}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}input::placeholder,textarea::placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='rgba(var(--gray-500), var(--tw-stroke-opacity, 1))' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:#0000;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:#0000;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active){[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:#0000;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}:root.dark{color-scheme:dark}[data-field-wrapper]{scroll-margin-top:8rem}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose-sm :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.1428571em}.prose-base :where(.prose-base>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose-base>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose-base>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose-base>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose-lg :where(.prose-lg>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose-lg>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose-lg>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose-lg>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.3333333em}.\!static{position:static!important}.mb-8{margin-bottom:2rem}.mr-2{margin-right:.5rem}.mt-4{margin-top:1rem}.h-48{height:12rem}.animate-\[spin_5s_linear_infinite\]{animation:spin 5s linear infinite}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.place-content-center{place-content:center}.border-4{border-width:4px}.\!border-primary-500{--tw-border-opacity:1!important;border-color:rgba(var(--primary-500),var(--tw-border-opacity))!important}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity:1;background-color:rgb(248 113 113/var(--tw-bg-opacity))}.object-contain{-o-object-fit:contain;object-fit:contain}.text-blue-900{--tw-text-opacity:1;color:rgb(30 58 138/var(--tw-text-opacity))}.text-neutral-100{--tw-text-opacity:1;color:rgb(245 245 245/var(--tw-text-opacity))}.btn-ig-edit,.btn-ig-generate{color:#fff!important}.btn-ig-generate{background-color:#7a4fa2!important}.btn-ig-generate:hover{background-color:#ac7dd2!important}.btn-ig-edit{background-color:#ad2160!important}.btn-ig-edit:hover{background-color:#e05297!important}.focus-visible\:ring-1:focus-visible,.focus-visible\:ring-2:focus-visible{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.400\)\]:disabled::-moz-placeholder,.disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.400\)\]:disabled::placeholder{-webkit-text-fill-color:rgba(var(--gray-400),1)}.group\/item:focus-visible .group-focus-visible\/item\:underline,.group\/link:focus-visible .group-focus-visible\/link\:underline{text-decoration-line:underline}.dark\:bg-blue-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 64 175/var(--tw-bg-opacity))}.dark\:bg-neutral-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(38 38 38/var(--tw-bg-opacity))}.dark\:text-blue-100:is(.dark *){--tw-text-opacity:1;color:rgb(219 234 254/var(--tw-text-opacity))}.dark\:disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.500\)\]:disabled:is(.dark *)::-moz-placeholder,.dark\:disabled\:placeholder\:\[-webkit-text-fill-color\:theme\(colors\.gray\.500\)\]:disabled:is(.dark *)::placeholder{-webkit-text-fill-color:rgba(var(--gray-500),1)}@media (min-width:1024px){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}}:checked+*>.\[\:checked\+\*\>\&\]\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}input:checked+.\[input\:checked\+\&\]\:bg-custom-600{--tw-bg-opacity:1;background-color:rgba(var(--c-600),var(--tw-bg-opacity))}input:checked+.\[input\:checked\+\&\]\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}input:checked+.\[input\:checked\+\&\]\:ring-0{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}input:checked+.\[input\:checked\+\&\]\:hover\:bg-custom-500:hover,input:checked+.dark\:\[input\:checked\+\&\]\:bg-custom-500:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--c-500),var(--tw-bg-opacity))}input:checked+.dark\:\[input\:checked\+\&\]\:hover\:bg-custom-400:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgba(var(--c-400),var(--tw-bg-opacity))}input:checked:focus-visible+.\[input\:checked\:focus-visible\+\&\]\:ring-custom-500\/50{--tw-ring-color:rgba(var(--c-500),0.5)}input:checked:focus-visible+.dark\:\[input\:checked\:focus-visible\+\&\]\:ring-custom-400\/50:is(.dark *){--tw-ring-color:rgba(var(--c-400),0.5)}input:focus-visible+.\[input\:focus-visible\+\&\]\:z-10{z-index:10}input:focus-visible+.\[input\:focus-visible\+\&\]\:ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}input:focus-visible+.\[input\:focus-visible\+\&\]\:ring-gray-950\/10{--tw-ring-color:rgba(var(--gray-950),0.1)}input:focus-visible+.dark\:\[input\:focus-visible\+\&\]\:ring-white\/20:is(.dark *){--tw-ring-color:#fff3} -------------------------------------------------------------------------------- /resources/dist/backend.js: -------------------------------------------------------------------------------- 1 | var Re=Object.create;var re=Object.defineProperty;var De=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var Me=Object.getPrototypeOf,je=Object.prototype.hasOwnProperty;var Xe=(S,b)=>()=>(b||S((b={exports:{}}).exports,b),b.exports);var Be=(S,b,g,h)=>{if(b&&typeof b=="object"||typeof b=="function")for(let f of Oe(b))!je.call(S,f)&&f!==g&&re(S,f,{get:()=>b[f],enumerable:!(h=De(b,f))||h.enumerable});return S};var Ue=(S,b,g)=>(g=S!=null?Re(Me(S)):{},Be(b||!S||!S.__esModule?re(g,"default",{value:S,enumerable:!0}):g,S));var se=Xe((D,V)=>{(function(S,b){if(typeof D=="object"&&typeof V=="object")V.exports=b();else if(typeof define=="function"&&define.amd)define([],b);else{var g=b();for(var h in g)(typeof D=="object"?D:S)[h]=g[h]}})(window,function(){return function(S){var b={};function g(h){if(b[h])return b[h].exports;var f=b[h]={i:h,l:!1,exports:{}};return S[h].call(f.exports,f,f.exports,g),f.l=!0,f.exports}return g.m=S,g.c=b,g.d=function(h,f,F){g.o(h,f)||Object.defineProperty(h,f,{enumerable:!0,get:F})},g.r=function(h){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(h,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(h,"__esModule",{value:!0})},g.t=function(h,f){if(1&f&&(h=g(h)),8&f||4&f&&typeof h=="object"&&h&&h.__esModule)return h;var F=Object.create(null);if(g.r(F),Object.defineProperty(F,"default",{enumerable:!0,value:h}),2&f&&typeof h!="string")for(var P in h)g.d(F,P,function(T){return h[T]}.bind(null,P));return F},g.n=function(h){var f=h&&h.__esModule?function(){return h.default}:function(){return h};return g.d(f,"a",f),f},g.o=function(h,f){return Object.prototype.hasOwnProperty.call(h,f)},g.p="",g(g.s=0)}([function(S,b,g){"use strict";g.r(b);var h,f="fslightbox-",F="".concat(f,"styles"),P="".concat(f,"cursor-grabbing"),T="".concat(f,"full-dimension"),k="".concat(f,"flex-centered"),_="".concat(f,"open"),Y="".concat(f,"transform-transition"),O="".concat(f,"absoluted"),J="".concat(f,"slide-btn"),G="".concat(J,"-container"),M="".concat(f,"fade-in"),j="".concat(f,"fade-out"),H=M+"-strong",$=j+"-strong",ae="".concat(f,"opacity-"),ce="".concat(ae,"1"),W="".concat(f,"source");function K(t){return(K=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function le(t){var e=t.stageIndexes,o=t.core.stageManager,r=t.props.sources.length-1;o.getPreviousSlideIndex=function(){return e.current===0?r:e.current-1},o.getNextSlideIndex=function(){return e.current===r?0:e.current+1},o.updateStageIndexes=r===0?function(){}:r===1?function(){e.current===0?(e.next=1,delete e.previous):(e.previous=0,delete e.next)}:function(){e.previous=o.getPreviousSlideIndex(),e.next=o.getNextSlideIndex()},o.i=r<=2?function(){return!0}:function(n){var i=e.current;if(i===0&&n===r||i===r&&n===0)return!0;var a=i-n;return a===-1||a===0||a===1}}(typeof document>"u"?"undefined":K(document))==="object"&&((h=document.createElement("style")).className=F,h.appendChild(document.createTextNode(".fslightbox-absoluted{position:absolute;top:0;left:0}.fslightbox-fade-in{animation:fslightbox-fade-in .3s cubic-bezier(0,0,.7,1)}.fslightbox-fade-out{animation:fslightbox-fade-out .3s ease}.fslightbox-fade-in-strong{animation:fslightbox-fade-in-strong .3s cubic-bezier(0,0,.7,1)}.fslightbox-fade-out-strong{animation:fslightbox-fade-out-strong .3s ease}@keyframes fslightbox-fade-in{from{opacity:.65}to{opacity:1}}@keyframes fslightbox-fade-out{from{opacity:.35}to{opacity:0}}@keyframes fslightbox-fade-in-strong{from{opacity:.3}to{opacity:1}}@keyframes fslightbox-fade-out-strong{from{opacity:1}to{opacity:0}}.fslightbox-cursor-grabbing{cursor:grabbing}.fslightbox-full-dimension{width:100%;height:100%}.fslightbox-open{overflow:hidden;height:100%}.fslightbox-flex-centered{display:flex;justify-content:center;align-items:center}.fslightbox-opacity-0{opacity:0!important}.fslightbox-opacity-1{opacity:1!important}.fslightbox-scrollbarfix{padding-right:17px}.fslightbox-transform-transition{transition:transform .3s}.fslightbox-container{font-family:Arial,sans-serif;position:fixed;top:0;left:0;background:linear-gradient(rgba(30,30,30,.9),#000 1810%);touch-action:pinch-zoom;z-index:1000000000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.fslightbox-container *{box-sizing:border-box}.fslightbox-svg-path{transition:fill .15s ease;fill:#ddd}.fslightbox-nav{height:45px;width:100%;position:absolute;top:0;left:0}.fslightbox-slide-number-container{display:flex;justify-content:center;align-items:center;position:relative;height:100%;font-size:15px;color:#d7d7d7;z-index:0;max-width:55px;text-align:left}.fslightbox-slide-number-container .fslightbox-flex-centered{height:100%}.fslightbox-slash{display:block;margin:0 5px;width:1px;height:12px;transform:rotate(15deg);background:#fff}.fslightbox-toolbar{position:absolute;z-index:3;right:0;top:0;height:100%;display:flex;background:rgba(35,35,35,.65)}.fslightbox-toolbar-button{height:100%;width:45px;cursor:pointer}.fslightbox-toolbar-button:hover .fslightbox-svg-path{fill:#fff}.fslightbox-slide-btn-container{display:flex;align-items:center;padding:12px 12px 12px 6px;position:absolute;top:50%;cursor:pointer;z-index:3;transform:translateY(-50%)}@media (min-width:476px){.fslightbox-slide-btn-container{padding:22px 22px 22px 6px}}@media (min-width:768px){.fslightbox-slide-btn-container{padding:30px 30px 30px 6px}}.fslightbox-slide-btn-container:hover .fslightbox-svg-path{fill:#f1f1f1}.fslightbox-slide-btn{padding:9px;font-size:26px;background:rgba(35,35,35,.65)}@media (min-width:768px){.fslightbox-slide-btn{padding:10px}}@media (min-width:1600px){.fslightbox-slide-btn{padding:11px}}.fslightbox-slide-btn-container-previous{left:0}@media (max-width:475.99px){.fslightbox-slide-btn-container-previous{padding-left:3px}}.fslightbox-slide-btn-container-next{right:0;padding-left:12px;padding-right:3px}@media (min-width:476px){.fslightbox-slide-btn-container-next{padding-left:22px}}@media (min-width:768px){.fslightbox-slide-btn-container-next{padding-left:30px}}@media (min-width:476px){.fslightbox-slide-btn-container-next{padding-right:6px}}.fslightbox-down-event-detector{position:absolute;z-index:1}.fslightbox-slide-swiping-hoverer{z-index:4}.fslightbox-invalid-file-wrapper{font-size:22px;color:#eaebeb;margin:auto}.fslightbox-video{object-fit:cover}.fslightbox-youtube-iframe{border:0}.fslightboxl{display:block;margin:auto;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:67px;height:67px}.fslightboxl div{box-sizing:border-box;display:block;position:absolute;width:54px;height:54px;margin:6px;border:5px solid;border-color:#999 transparent transparent transparent;border-radius:50%;animation:fslightboxl 1.2s cubic-bezier(.5,0,.5,1) infinite}.fslightboxl div:nth-child(1){animation-delay:-.45s}.fslightboxl div:nth-child(2){animation-delay:-.3s}.fslightboxl div:nth-child(3){animation-delay:-.15s}@keyframes fslightboxl{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.fslightbox-source{position:relative;z-index:2;opacity:0}")),document.head.appendChild(h));function ue(t){var e,o=t.props,r=0,n={};this.getSourceTypeFromLocalStorageByUrl=function(a){return e[a]?e[a]:i(a)},this.handleReceivedSourceTypeForUrl=function(a,c){if(n[c]===!1&&(r--,a!=="invalid"?n[c]=a:delete n[c],r===0)){(function(s,l){for(var u in l)s[u]=l[u]})(e,n);try{localStorage.setItem("fslightbox-types",JSON.stringify(e))}catch{}}};var i=function(a){r++,n[a]=!1};if(o.disableLocalStorage)this.getSourceTypeFromLocalStorageByUrl=function(){},this.handleReceivedSourceTypeForUrl=function(){};else{try{e=JSON.parse(localStorage.getItem("fslightbox-types"))}catch{}e||(e={},this.getSourceTypeFromLocalStorageByUrl=i)}}function de(t,e,o,r){var n=t.data,i=t.elements.sources,a=o/r,c=0;this.adjustSize=function(){if((c=n.maxSourceWidth/a)n.maxSourceHeight?n.maxSourceHeight:r,s()};var s=function(){i[e].style.width=c*a+"px",i[e].style.height=c+"px"}}function fe(t,e){var o=this,r=t.collections.sourceSizers,n=t.elements,i=n.sourceAnimationWrappers,a=n.sources,c=t.isl,s=t.resolve;function l(u,d){r[e]=s(de,[e,u,d]),r[e].adjustSize()}this.runActions=function(u,d){c[e]=!0,a[e].classList.add(ce),i[e].classList.add(H),i[e].removeChild(i[e].firstChild),l(u,d),o.runActions=l}}function pe(t,e){var o,r=this,n=t.elements.sources,i=t.props,a=(0,t.resolve)(fe,[e]);this.handleImageLoad=function(c){var s=c.target,l=s.naturalWidth,u=s.naturalHeight;a.runActions(l,u)},this.handleVideoLoad=function(c){var s=c.target,l=s.videoWidth,u=s.videoHeight;o=!0,a.runActions(l,u)},this.handleNotMetaDatedVideoLoad=function(){o||r.handleYoutubeLoad()},this.handleYoutubeLoad=function(){var c=1920,s=1080;i.maxYoutubeDimensions&&(c=i.maxYoutubeDimensions.width,s=i.maxYoutubeDimensions.height),a.runActions(c,s)},this.handleCustomLoad=function(){var c=n[e],s=c.offsetWidth,l=c.offsetHeight;s&&l?a.runActions(s,l):setTimeout(r.handleCustomLoad)}}function R(t,e,o){var r=t.elements.sources,n=t.props.customClasses,i=n[e]?n[e]:"";r[e].className=o+" "+i}function X(t,e){var o=t.elements.sources,r=t.props.customAttributes;for(var n in r[e])o[e].setAttribute(n,r[e][n])}function he(t,e){var o=t.collections.sourceLoadHandlers,r=t.elements,n=r.sources,i=r.sourceAnimationWrappers,a=t.props.sources;n[e]=document.createElement("img"),R(t,e,W),n[e].src=a[e],n[e].onload=o[e].handleImageLoad,X(t,e),i[e].appendChild(n[e])}function me(t,e){var o=t.collections.sourceLoadHandlers,r=t.elements,n=r.sources,i=r.sourceAnimationWrappers,a=t.props,c=a.sources,s=a.videosPosters;n[e]=document.createElement("video"),R(t,e,W),n[e].src=c[e],n[e].onloadedmetadata=function(u){o[e].handleVideoLoad(u)},n[e].controls=!0,X(t,e),s[e]&&(n[e].poster=s[e]);var l=document.createElement("source");l.src=c[e],n[e].appendChild(l),setTimeout(o[e].handleNotMetaDatedVideoLoad,3e3),i[e].appendChild(n[e])}function ge(t,e){var o=t.collections.sourceLoadHandlers,r=t.elements,n=r.sources,i=r.sourceAnimationWrappers,a=t.props.sources;n[e]=document.createElement("iframe"),R(t,e,"".concat(W," ").concat(f,"youtube-iframe"));var c=a[e],s=c.split("?")[1];n[e].src="https://www.youtube.com/embed/".concat(c.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/)[2],"?").concat(s||""),n[e].allowFullscreen=!0,X(t,e),i[e].appendChild(n[e]),o[e].handleYoutubeLoad()}function ve(t,e){var o=t.collections.sourceLoadHandlers,r=t.elements,n=r.sources,i=r.sourceAnimationWrappers,a=t.props.sources;n[e]=a[e],R(t,e,"".concat(n[e].className," ").concat(W)),i[e].appendChild(n[e]),o[e].handleCustomLoad()}function be(t,e){var o=t.elements,r=o.sources,n=o.sourceAnimationWrappers;t.props.sources,r[e]=document.createElement("div"),r[e].className="".concat(f,"invalid-file-wrapper ").concat(k),r[e].innerHTML="Invalid source",n[e].classList.add(H),n[e].removeChild(n[e].firstChild),n[e].appendChild(r[e])}function xe(t){var e=t.collections,o=e.sourceLoadHandlers,r=e.sourcesRenderFunctions,n=t.core.sourceDisplayFacade,i=t.resolve;this.runActionsForSourceTypeAndIndex=function(a,c){var s;switch(a!=="invalid"&&(o[c]=i(pe,[c])),a){case"image":s=he;break;case"video":s=me;break;case"youtube":s=ge;break;case"custom":s=ve;break;default:s=be}r[c]=function(){return s(t,c)},n.displaySourcesWhichShouldBeDisplayed()}}function ye(){var t,e,o,r={isUrlYoutubeOne:function(i){var a=document.createElement("a");return a.href=i,a.hostname==="www.youtube.com"||a.hostname==="youtu.be"},getTypeFromResponseContentType:function(i){return i.slice(0,i.indexOf("/"))}};function n(){if(o.readyState!==4){if(o.readyState===2){var i;switch(r.getTypeFromResponseContentType(o.getResponseHeader("content-type"))){case"image":i="image";break;case"video":i="video";break;default:i="invalid"}o.onreadystatechange=null,o.abort(),e(i)}}else e("invalid")}this.setUrlToCheck=function(i){t=i},this.getSourceType=function(i){if(r.isUrlYoutubeOne(t))return i("youtube");e=i,(o=new XMLHttpRequest).onreadystatechange=n,o.open("GET",t,!0),o.send()}}function we(t,e,o){var r=t.props,n=r.types,i=r.type,a=r.sources,c=t.resolve;this.getTypeSetByClientForIndex=function(s){var l;return n&&n[s]?l=n[s]:i&&(l=i),l},this.retrieveTypeWithXhrForIndex=function(s){var l=c(ye);l.setUrlToCheck(a[s]),l.getSourceType(function(u){e.handleReceivedSourceTypeForUrl(u,a[s]),o.runActionsForSourceTypeAndIndex(u,s)})}}function Se(t,e){var o=t.core.stageManager,r=t.elements,n=r.smw,i=r.sourceWrappersContainer,a=t.props,c=0,s=document.createElement("div");function l(d){s.style.transform="translateX(".concat(d+c,"px)"),c=0}function u(){return(1+a.slideDistance)*innerWidth}s.className="".concat(O," ").concat(T," ").concat(k),s.s=function(){s.style.display="flex"},s.h=function(){s.style.display="none"},s.a=function(){s.classList.add(Y)},s.d=function(){s.classList.remove(Y)},s.n=function(){s.style.removeProperty("transform")},s.v=function(d){return c=d,s},s.ne=function(){l(-u())},s.z=function(){l(0)},s.p=function(){l(u())},o.i(e)||s.h(),n[e]=s,i.appendChild(s),function(d,p){var w=d.elements,x=w.smw,L=w.sourceAnimationWrappers,m=document.createElement("div"),y=document.createElement("div");y.className="fslightboxl";for(var v=0;v<3;v++){var E=document.createElement("div");y.appendChild(E)}m.appendChild(y),x[p].appendChild(m),L[p]=m}(t,e)}function B(t,e,o,r){var n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttributeNS(null,"width",e),n.setAttributeNS(null,"height",e),n.setAttributeNS(null,"viewBox",o);var i=document.createElementNS("http://www.w3.org/2000/svg","path");return i.setAttributeNS(null,"class","".concat(f,"svg-path")),i.setAttributeNS(null,"d",r),n.appendChild(i),t.appendChild(n),n}function Q(t,e){var o=document.createElement("div");return o.className="".concat(f,"toolbar-button ").concat(k),o.title=e,t.appendChild(o),o}function Le(t,e){var o=document.createElement("div");o.className="".concat(f,"toolbar"),e.appendChild(o),function(r,n){var i=r.componentsServices,a=r.data,c=r.fs,s="M4.5 11H3v4h4v-1.5H4.5V11zM3 7h1.5V4.5H7V3H3v4zm10.5 6.5H11V15h4v-4h-1.5v2.5zM11 3v1.5h2.5V7H15V3h-4z",l=Q(n);l.title="Enter fullscreen";var u=B(l,"20px","0 0 18 18",s);i.ofs=function(){a.ifs=!0,l.title="Exit fullscreen",u.setAttributeNS(null,"width","24px"),u.setAttributeNS(null,"height","24px"),u.setAttributeNS(null,"viewBox","0 0 950 1024"),u.firstChild.setAttributeNS(null,"d","M682 342h128v84h-212v-212h84v128zM598 810v-212h212v84h-128v128h-84zM342 342v-128h84v212h-212v-84h128zM214 682v-84h212v212h-84v-128h-128z")},i.xfs=function(){a.ifs=!1,l.title="Enter fullscreen",u.setAttributeNS(null,"width","20px"),u.setAttributeNS(null,"height","20px"),u.setAttributeNS(null,"viewBox","0 0 18 18"),u.firstChild.setAttributeNS(null,"d",s)},l.onclick=c.t}(t,o),function(r,n){var i=Q(n,"Close");i.onclick=r.core.lightboxCloser.closeLightbox,B(i,"20px","0 0 24 24","M 4.7070312 3.2929688 L 3.2929688 4.7070312 L 10.585938 12 L 3.2929688 19.292969 L 4.7070312 20.707031 L 12 13.414062 L 19.292969 20.707031 L 20.707031 19.292969 L 13.414062 12 L 20.707031 4.7070312 L 19.292969 3.2929688 L 12 10.585938 L 4.7070312 3.2929688 z")}(t,o)}function Ae(t){var e=t.props.sources,o=t.elements.container,r=document.createElement("div");r.className="".concat(f,"nav"),o.appendChild(r),Le(t,r),e.length>1&&function(n,i){var a=n.componentsServices,c=n.props.sources,s=(n.stageIndexes,document.createElement("div"));s.className="".concat(f,"slide-number-container");var l=document.createElement("div");l.className=k;var u=document.createElement("span");a.setSlideNumber=function(w){return u.innerHTML=w};var d=document.createElement("span");d.className="".concat(f,"slash");var p=document.createElement("div");p.innerHTML=c.length,s.appendChild(l),l.appendChild(u),l.appendChild(d),l.appendChild(p),i.appendChild(s),setTimeout(function(){l.offsetWidth>55&&(s.style.justifyContent="flex-start")})}(t,r)}function Z(t,e,o,r){var n=t.elements.container,i=o.charAt(0).toUpperCase()+o.slice(1),a=document.createElement("div");a.className="".concat(G," ").concat(G,"-").concat(o),a.title="".concat(i," slide"),a.onclick=e,function(c,s){var l=document.createElement("div");l.className="".concat(J," ").concat(k),B(l,"20px","0 0 20 20",s),c.appendChild(l)}(a,r),n.appendChild(a)}function Ce(t){var e=t.core,o=e.lightboxCloser,r=e.slideChangeFacade,n=t.fs;this.listener=function(i){switch(i.key){case"Escape":o.closeLightbox();break;case"ArrowLeft":r.changeToPrevious();break;case"ArrowRight":r.changeToNext();break;case"F11":i.preventDefault(),n.t()}}}function Ee(t){var e=t.elements,o=t.sourcePointerProps,r=t.stageIndexes;function n(i,a){e.smw[i].v(o.swipedX)[a]()}this.runActionsForEvent=function(i){var a,c,s;e.container.contains(e.slideSwipingHoverer)||e.container.appendChild(e.slideSwipingHoverer),a=e.container,c=P,(s=a.classList).contains(c)||s.add(c),o.swipedX=i.screenX-o.downScreenX;var l=r.previous,u=r.next;n(r.current,"z"),l!==void 0&&o.swipedX>0?n(l,"ne"):u!==void 0&&o.swipedX<0&&n(u,"p")}}function Fe(t){var e=t.props.sources,o=t.resolve,r=t.sourcePointerProps,n=o(Ee);e.length===1?this.listener=function(){r.swipedX=1}:this.listener=function(i){r.isPointering&&n.runActionsForEvent(i)}}function Ie(t){var e=t.core.slideIndexChanger,o=t.elements.smw,r=t.stageIndexes,n=t.sws;function i(c){var s=o[r.current];s.a(),s[c]()}function a(c,s){c!==void 0&&(o[c].s(),o[c][s]())}this.runPositiveSwipedXActions=function(){var c=r.previous;if(c===void 0)i("z");else{i("p");var s=r.next;e.changeTo(c);var l=r.previous;n.d(l),n.b(s),i("z"),a(l,"ne")}},this.runNegativeSwipedXActions=function(){var c=r.next;if(c===void 0)i("z");else{i("ne");var s=r.previous;e.changeTo(c);var l=r.next;n.d(l),n.b(s),i("z"),a(l,"p")}}}function ee(t,e){t.contains(e)&&t.removeChild(e)}function Te(t){var e=t.core.lightboxCloser,o=t.elements,r=t.resolve,n=t.sourcePointerProps,i=r(Ie);this.runNoSwipeActions=function(){ee(o.container,o.slideSwipingHoverer),n.isSourceDownEventTarget||e.closeLightbox(),n.isPointering=!1},this.runActions=function(){n.swipedX>0?i.runPositiveSwipedXActions():i.runNegativeSwipedXActions(),ee(o.container,o.slideSwipingHoverer),o.container.classList.remove(P),n.isPointering=!1}}function Ne(t){var e=t.resolve,o=t.sourcePointerProps,r=e(Te);this.listener=function(){o.isPointering&&(o.swipedX?r.runActions():r.runNoSwipeActions())}}function ze(t){var e=this,o=t.core,r=o.eventsDispatcher,n=o.globalEventsController,i=o.scrollbarRecompensor,a=t.data,c=t.elements,s=t.fs,l=t.props,u=t.sourcePointerProps;this.isLightboxFadingOut=!1,this.runActions=function(){e.isLightboxFadingOut=!0,c.container.classList.add($),n.removeListeners(),l.exitFullscreenOnClose&&a.ifs&&s.x(),setTimeout(function(){e.isLightboxFadingOut=!1,u.isPointering=!1,c.container.classList.remove($),document.documentElement.classList.remove(_),i.removeRecompense(),document.body.removeChild(c.container),r.dispatch("onClose")},270)}}function U(t,e){var o=t.classList;o.contains(e)&&o.remove(e)}function Pe(t){var e,o,r;o=(e=t).core.eventsDispatcher,r=e.props,o.dispatch=function(n){r[n]&&r[n]()},function(n){var i=n.componentsServices,a=n.data,c=n.fs,s=["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"];function l(d){for(var p=0;pinnerHeight&&(document.body.style.marginRight=i.scrollbarWidth+"px")}a.addRecompense=function(){document.readyState==="complete"?c():addEventListener("load",function(){c(),a.addRecompense=c})},a.removeRecompense=function(){document.body.style.removeProperty("margin-right")}}(t),function(n){var i=n.core,a=i.slideChangeFacade,c=i.slideIndexChanger,s=i.stageManager;n.props.sources.length>1?(a.changeToPrevious=function(){c.jumpTo(s.getPreviousSlideIndex())},a.changeToNext=function(){c.jumpTo(s.getNextSlideIndex())}):(a.changeToPrevious=function(){},a.changeToNext=function(){})}(t),function(n){var i=n.componentsServices,a=n.core,c=a.slideIndexChanger,s=a.sourceDisplayFacade,l=a.stageManager,u=n.elements,d=u.smw,p=u.sourceAnimationWrappers,w=n.isl,x=n.stageIndexes,L=n.sws;c.changeTo=function(m){x.current=m,l.updateStageIndexes(),i.setSlideNumber(m+1),s.displaySourcesWhichShouldBeDisplayed()},c.jumpTo=function(m){var y=x.previous,v=x.current,E=x.next,A=w[v],I=w[m];c.changeTo(m);for(var C=0;C1&&(L=(x=t).core.slideChangeFacade,Z(x,L.changeToPrevious,"previous","M18.271,9.212H3.615l4.184-4.184c0.306-0.306,0.306-0.801,0-1.107c-0.306-0.306-0.801-0.306-1.107,0L1.21,9.403C1.194,9.417,1.174,9.421,1.158,9.437c-0.181,0.181-0.242,0.425-0.209,0.66c0.005,0.038,0.012,0.071,0.022,0.109c0.028,0.098,0.075,0.188,0.142,0.271c0.021,0.026,0.021,0.061,0.045,0.085c0.015,0.016,0.034,0.02,0.05,0.033l5.484,5.483c0.306,0.307,0.801,0.307,1.107,0c0.306-0.305,0.306-0.801,0-1.105l-4.184-4.185h14.656c0.436,0,0.788-0.353,0.788-0.788S18.707,9.212,18.271,9.212z"),Z(x,L.changeToNext,"next","M1.729,9.212h14.656l-4.184-4.184c-0.307-0.306-0.307-0.801,0-1.107c0.305-0.306,0.801-0.306,1.106,0l5.481,5.482c0.018,0.014,0.037,0.019,0.053,0.034c0.181,0.181,0.242,0.425,0.209,0.66c-0.004,0.038-0.012,0.071-0.021,0.109c-0.028,0.098-0.075,0.188-0.143,0.271c-0.021,0.026-0.021,0.061-0.045,0.085c-0.015,0.016-0.034,0.02-0.051,0.033l-5.483,5.483c-0.306,0.307-0.802,0.307-1.106,0c-0.307-0.305-0.307-0.801,0-1.105l4.184-4.185H1.729c-0.436,0-0.788-0.353-0.788-0.788S1.293,9.212,1.729,9.212z")),function(m){for(var y=m.props.sources,v=m.resolve,E=v(ue),A=v(xe),I=v(we,[E,A]),C=0;C0&&arguments[0]!==void 0?arguments[0]:0,L=d.previous,m=d.current,y=d.next;d.current=x,l.i||le(t),c.updateStageIndexes(),l.i?(p.c(),p.a(),p.b(L),p.b(m),p.b(y),r.dispatch("onShow")):w(),a.displaySourcesWhichShouldBeDisplayed(),e.setSlideNumber(x+1),document.body.appendChild(u.container),document.documentElement.classList.add(_),i.addRecompense(),n.attachListeners(),s.runActions(),u.smw[d.current].n(),r.dispatch("onOpen")}}function te(t,e,o){return(te=He()?Reflect.construct.bind():function(r,n,i){var a=[null];a.push.apply(a,n);var c=new(Function.bind.apply(r,a));return i&&ne(c,i.prototype),c}).apply(null,arguments)}function He(){if(typeof Reflect>"u"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function ne(t,e){return(ne=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(o,r){return o.__proto__=r,o})(t,e)}function We(t){return function(e){if(Array.isArray(e))return q(e)}(t)||function(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}(t)||function(e,o){if(e){if(typeof e=="string")return q(e,o);var r=Object.prototype.toString.call(e).slice(8,-1);if(r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set")return Array.from(e);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return q(e,o)}}(t)||function(){throw new TypeError(`Invalid attempt to spread non-iterable instance. 2 | In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function q(t,e){(e==null||e>t.length)&&(e=t.length);for(var o=0,r=new Array(e);o1&&arguments[1]!==void 0?arguments[1]:[];return o.unshift(t),te(e,We(o))},this.collections={sourceLoadHandlers:[],sourcesRenderFunctions:[],sourceSizers:[]},this.core={eventsDispatcher:{},globalEventsController:{},lightboxCloser:{},lightboxUpdater:{},scrollbarRecompensor:{},slideChangeFacade:{},slideIndexChanger:{},sourcesPointerDown:{},sourceDisplayFacade:{},stageManager:{},windowResizeActioner:{}},this.fs={},this.sws={},ke(this),this.close=function(){return t.core.lightboxCloser.closeLightbox()}},window.fsLightboxInstances={},oe(),window.refreshFsLightbox=function(){for(var t in fsLightboxInstances){var e=fsLightboxInstances[t].props;fsLightboxInstances[t]=new FsLightbox,fsLightboxInstances[t].props=e,fsLightboxInstances[t].props.sources=[],fsLightboxInstances[t].elements.a=[]}oe()}}])})});var Ve=Ue(se(),1); 3 | --------------------------------------------------------------------------------