(function($, elementor) { 'use strict'; // Ensure AI field localization exists if (!window.KingAddonsAiImageField) { return; } var data = window.KingAddonsAiImageField; /** * Injects the Generate Image button and prompt UI into media controls * @param {jQuery} $container Elementor panel container */ function injectImageControls($container) { $container.find( '.elementor-control.elementor-control-type-media, ' + '.elementor-control.elementor-control-type-image, ' + '.elementor-control.elementor-control-type-gallery' ).each(function() { var $ctrl = $(this); // Skip if already injected if ($ctrl.find('.kng-ai-image-btn-wrapper').length) { return; } // Find the label of the media field (not nested labels like Resolution) var $mediaTitle = $ctrl.find('.elementor-control-field.elementor-control-media > .elementor-control-title').first(); if (!$mediaTitle.length) { // fallback to any control title $mediaTitle = $ctrl.find('.elementor-control-title').first(); } // Wrap button and prompt in container var $outer = $('
'); // Insert button/prompt wrapper after the input wrapper if available, otherwise after the title var $inputWrapper = $ctrl.find('.elementor-control-input-wrapper').first(); if ($inputWrapper.length) { $inputWrapper.after($outer); } else { $mediaTitle.after($outer); } var $wrapper = $('
'); // Button with icon, matching AI text feature var $btn = $(''); $outer.append($wrapper); $wrapper.append($btn); $btn.on('click', function(e) { e.preventDefault(); // Remove existing prompt UI $outer.find('.kng-ai-image-prompt-container').remove(); // FIRST_EDIT: Check API key validity before proceeding var apiKeyValid = true; $.ajax({ url: KingAddonsAiImageField.ajax_url, method: 'POST', async: false, dataType: 'json', data: { action: 'king_addons_ai_image_check_limits', nonce: KingAddonsAiImageField.generate_nonce }, success: function(resp) { apiKeyValid = resp.success && resp.data.api_key_valid === true; }, error: function() { apiKeyValid = true; // on error assume valid } }); if (!apiKeyValid) { var settingsUrl = window.KingAddonsAiImageField && window.KingAddonsAiImageField.settings_url ? window.KingAddonsAiImageField.settings_url : '/wp-admin/admin.php?page=king-addons-ai-settings'; var $errorMessage = $('
').css({ background: '#e7f3fe', color: '#084d7a', padding: '10px 15px', borderRadius: '4px', border: '1px solid #b6e0fe', margin: '8px 0', fontSize: '13px', fontWeight: '500', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }); $errorMessage.html( 'OpenAI API key is missing or invalid. Please configure your API key in AI Settings.' + 'Settings' ); $wrapper.after($errorMessage).hide().fadeIn(200); setTimeout(function() { $errorMessage.fadeOut(200, function() { $(this).remove(); }); }, 5000); return; } // Proceed to show prompt UI // Create prompt UI var $promptContainer = $('
').css({margin:'8px 0'}); var $input = $(''); var $quality = $(''); ['hd','standard'].forEach(function(q) { $quality.append(''); }); var $resolution = $(''); // Define resolutions with display labels var resolutions = [ { value: '1024x1024', label: '1024×1024 (square)' }, { value: '1024x1792', label: '1024×1792 (portrait)' }, { value: '1792x1024', label: '1792×1024 (landscape)' } ]; resolutions.forEach(function(res) { $resolution.append(''); }); // Model selector reflecting saved default var $modelSelect = $(''); var imageModels = [ { value: 'dall-e-3', label: 'DALL·E 3' }, { value: 'gpt-image-1', label: 'GPT Image 1' } ]; imageModels.forEach(function(m) { $modelSelect.append(''); }); // Apply default from settings if (KingAddonsAiImageField.image_model) { $modelSelect.val(KingAddonsAiImageField.image_model); } // Define controls per model var modelControls = { 'dall-e-3': { qualities: [ { value: 'hd', label: 'HD (default)' }, { value: 'standard', label: 'Standard' } ], sizes: [ { value: '1024x1024', label: '1024×1024 (square)' }, { value: '1024x1792', label: '1024×1792 (portrait)' }, { value: '1792x1024', label: '1792×1024 (landscape)' } ] }, 'gpt-image-1': { qualities: [ { value: 'high', label: 'High (default)' }, { value: 'medium', label: 'Medium' }, { value: 'low', label: 'Low' }, { value: 'auto', label: 'Auto' }, ], sizes: [ { value: 'auto', label: 'Auto (default)' }, { value: '1024x1024', label: '1024×1024 (square)' }, { value: '1536x1024', label: '1536×1024 (landscape)' }, { value: '1024x1536', label: '1024×1536 (portrait)' } ] } }; // Function to update selects based on chosen model function updateControlsByModel() { var m = $modelSelect.val(); // update quality options $quality.empty(); modelControls[m].qualities.forEach(function(opt) { $quality.append(''); }); // update size options $resolution.empty(); modelControls[m].sizes.forEach(function(opt) { $resolution.append(''); }); } // Bind change handler and initialize $modelSelect.on('change', updateControlsByModel); updateControlsByModel(); // Create transparent background checkbox (only for GPT Image 1) var $bgCheckbox = $(''); // Update show/hide of background checkbox function updateBgCheckbox() { if ($modelSelect.val() === 'gpt-image-1') { $bgCheckbox.show(); } else { $bgCheckbox.hide().find('input').prop('checked', false); } } $modelSelect.on('change', updateBgCheckbox); updateBgCheckbox(); // Prompt submit button with icon var $submit = $('').css({marginRight:'6px'}); // Prompt cancel button with cross icon var $cancel = $(''); // Append input and labelled rows for each control and buttons $promptContainer.append( $input, $('
').append( '', $quality ), $('
').append( '', $resolution ), $('
').append( '', $modelSelect ), $('
').append($bgCheckbox), $('
').append( $submit, $cancel ) ); $outer.append($promptContainer); // Add user note about processing time $promptContainer.append($('

Complex prompts may take up to 2 minutes to process.

')); // Mark the Generate Image button as active (container open) $btn.addClass('is-active'); $submit.on('click', function() { var prompt = $input.val().trim(); if (!prompt) { $input.css('border-color','red'); return; } var selectedResolution = $resolution.val(); var selectedQuality = $quality.val(); var selectedModel = $modelSelect.val(); // Disable UI and animate while processing $submit.prop('disabled', true).addClass('is-processing'); // Change button text preserving icon $submit.find('span').text('Generating...'); // Animate image preview border var $preview = $ctrl.find('.elementor-control-media__preview'); if ($preview.length) { $preview.addClass('king-addons-field-pulsing'); } // AJAX request $.post(KingAddonsAiImageField.ajax_url, { action: KingAddonsAiImageField.generate_action, nonce: KingAddonsAiImageField.generate_nonce, prompt: prompt, quality: selectedQuality, size: selectedResolution, model: selectedModel, background: $bgCheckbox.find('input').is(':checked') ? 'transparent' : '', }).done(function(response) { if (response.success && response.data.attachment_id) { var attachmentID = response.data.attachment_id; var imageURL = response.data.url; // Get the current element being edited var currentElement = elementor.getPanelView().getCurrentPageView().getOption('editedElementView'); if (currentElement) { // Get the control name from the hidden input var controlName = $ctrl.find('input[type="hidden"][data-setting]').data('setting'); if (controlName) { // Create the image data object var imageData = { id: attachmentID, url: imageURL }; // Update the element's settings var settings = {}; settings[controlName] = imageData; // Use $e.run to properly update the element $e.run('document/elements/settings', { container: currentElement.getContainer(), settings: settings }); // Update the control preview if ($preview.length) { $preview.css('background-image', 'url(' + imageURL + ')'); } } } } else { alert('Error generating image. ' + response.responseJSON.data.message); } }).fail(function(response) { alert('Error generating image. ' + response.responseJSON.data.message); }).always(function() { // Clean up animations and UI $submit.prop('disabled', false).removeClass('is-processing').find('span').text('Generate'); if (typeof $preview !== 'undefined' && $preview.length) { $preview.removeClass('king-addons-field-pulsing'); } // Remove active state on complete $btn.removeClass('is-active'); $promptContainer.remove(); }); }); $cancel.on('click', function() { // Remove active state when closing container $btn.removeClass('is-active'); $promptContainer.remove(); }); }); }); } // Global observer: watch for any media control fields added anywhere var __kngGlobalObserver = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { Array.prototype.forEach.call(mutation.addedNodes, function(node) { if (node.nodeType !== 1) { return; } var $node = $(node); // Check for media field or containing media field var $mediaField = $node.is('.elementor-control-field.elementor-control-media') ? $node : $node.find('.elementor-control-field.elementor-control-media'); if (!$mediaField.length) { return; } // Find the parent control wrapper and its panel var $ctrl = $mediaField.closest('.elementor-control.elementor-control-type-media, .elementor-control.elementor-control-type-image, .elementor-control.elementor-control-type-gallery'); if (!$ctrl.length) { return; } var $panel = $ctrl.closest('.elementor-panel'); if ($panel.length) { injectImageControls($panel); } }); }); }); __kngGlobalObserver.observe(document.body, { childList: true, subtree: true }); // On widget panel open, inject image controls and watch for dynamically added controls elementor.hooks.addAction('panel/open_editor/widget', function(panel) { var $panelEl = panel.$el; // Initial injection after panel renders setTimeout(function() { injectImageControls($panelEl); }, 250); // Setup a MutationObserver to catch lazy-loaded controls var root = $panelEl[0]; if (!root.__kngAiObserver) { var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { Array.prototype.forEach.call(mutation.addedNodes, function(node) { if (node.nodeType === 1 && (node.matches('.elementor-control.elementor-control-type-media, .elementor-control.elementor-control-type-image, .elementor-control.elementor-control-type-gallery') || $(node).find('.elementor-control.elementor-control-type-media, .elementor-control.elementor-control-type-image, .elementor-control.elementor-control-type-gallery').length)) { injectImageControls($panelEl); } }); }); }); observer.observe(root, { childList: true, subtree: true }); root.__kngAiObserver = observer; } // Extra delayed injections to catch any late-rendered controls setTimeout(function() { injectImageControls($panelEl); }, 800); setTimeout(function() { injectImageControls($panelEl); }, 1500); }); // Also monitor section changes (e.g., switching tabs) elementor.channels.editor.on('section:activated', function(sectionName, editor) { var panelView = editor.getOption('editedElementView').getContainer().panel; if (panelView && panelView.$el) { setTimeout(function() { injectImageControls(panelView.$el); }, 150); } }); // Re-inject image controls whenever a control view is opened elementor.hooks.addAction('panel/open_editor/control', function(controlView) { var panelEl = controlView.$el.closest('.elementor-panel'); if (panelEl.length) { injectImageControls(panelEl); } }); // Listen for Background Type toggles and re-inject Generate Image buttons when sub-controls appear $(document).on('change', '.elementor-control.elementor-control-background_background input[type="radio"]', function() { var panelEl = $(this).closest('.elementor-panel'); setTimeout(function() { injectImageControls(panelEl); }, 100); }); })(jQuery, window.elementor);