How to Use AJAX in WordPress Widget on Admin Backend only?

I’m trying to create a simple WordPress widget for AI text generation. The widget should allow the user to type a prompt, set a word limit, and click a “Generate Text” button. I want to use the OpenAI API to generate text based on the prompt and word limit. However, I’m facing an issue with how the widget behaves depending on where it’s loaded.

Problem:

The AI text generation works fine when all the options (form) are loaded on the WordPress front page (working example in CODE-1 below). But I want the widget to only be available in the backend (as part of the widget options).

So, my goal is to make the AI text generator available only in the backend for editing widgets, and have the API generate text and insert it into the widget’s text area. The main issue is that AJAX doesn’t seem to work properly within widget options, and I’m struggling to find a solution.

What I want to achieve:

  • Create a widget in the WordPress backend.
  • Use the OpenAI API to generate text based on user input (prompt and
    word limit).
  • Insert the generated text into the widget text area.

Questions:

How can I make AJAX work properly inside the WordPress widget options in the backend?
Is there a recommended approach for integrating OpenAI API text generation with a WordPress widget in the backend?
I’m not sure what else to check or why API requests can’t be handled through the backend widget options. I tested AI text generation on a backend page (not widgets), and it works fine there. The issue is only with the widgets, and I can’t figure out why it’s not working. There are no errors in the console, nothing—it just doesn’t work in the widget.

Any help would be greatly appreciated!

CODE-1 (works OK if loaded as front-end widget):

<?php
/*
Plugin Name: OpenAI Widget
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        echo $args['before_widget'];
        ?>
        <div class="openai-widget">
            <label for="openai_prompt">Prompt:</label>
            <textarea id="openai_prompt" rows="3"></textarea>

            <label for="openai_word_limit">Word Limit:</label>
            <input type="number" id="openai_word_limit" value="100" min="1" max="500"/>

            <button id="openai_generate">Generate Text</button>

            <label for="openai_output">Output:</label>
            <textarea id="openai_output" rows="5" readonly></textarea>
        </div>

        <script>
document.addEventListener("DOMContentLoaded", function() {
    document.getElementById("openai_generate").addEventListener("click", function() {
        let prompt = document.getElementById("openai_prompt").value.trim();
        let wordLimit = parseInt(document.getElementById("openai_word_limit").value);

        if (prompt.length === 0) {
            alert("Please enter a prompt.");
            return;
        }

        fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: `action=openai_generate_text&prompt=${encodeURIComponent(prompt)}&word_limit=${wordLimit}`
        })
        .then(response => response.json())
        .then(data => {
            console.log(data);  // Debugging: log the response data

            // Check if the response is as expected
            if (data.success) {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = data.data.text || "No text generated.";  // Access text inside `data.data.text`
                } else {
                    console.error('Output textarea not found.');
                }
            } else {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = "Error: " + (data.message || "Unknown error");
                } else {
                    console.error('Output textarea not found.');
                }
            }
        })
        .catch(error => {
            console.error("Error:", error);
            const output = document.getElementById("openai_output");
            if (output) {
                output.value = "Error: Unable to reach API or process request.";
            }
        });
    });
});


        </script>
        <?php
        echo $args['after_widget'];
    }

    public function form($instance) {
        echo '<p>No settings required.</p>';
    }

    public function update($new_instance, $old_instance) {
        return $new_instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Debugging: Log the full response body to see the structure of the response
    error_log('OpenAI Response: ' . print_r($body, true));

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

CODE-2 (loaded in widgets backend options – not working):

<?php
/*
Plugin Name: OpenAI Widget - backend options
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        // On the front-end, just show the widget without any fields or outputs.
        echo $args['before_widget'];
        echo $args['after_widget'];
    }

    public function form($instance) {
        // Backend form
        $prompt = !empty($instance['prompt']) ? $instance['prompt'] : '';
        $word_limit = !empty($instance['word_limit']) ? $instance['word_limit'] : 100;
        ?>
        <p>
            <label for="<?php echo $this->get_field_id('prompt'); ?>"><?php _e('Prompt:'); ?></label>
            <textarea class="widefat" id="<?php echo $this->get_field_id('prompt'); ?>" name="<?php echo $this->get_field_name('prompt'); ?>" rows="3"><?php echo esc_attr($prompt); ?></textarea>
        </p>
        <p>
            <label for="<?php echo $this->get_field_id('word_limit'); ?>"><?php _e('Word Limit:'); ?></label>
            <input class="widefat" id="<?php echo $this->get_field_id('word_limit'); ?>" name="<?php echo $this->get_field_name('word_limit'); ?>" type="number" value="<?php echo esc_attr($word_limit); ?>" min="1" max="500" />
        </p>
        <p>
            <!-- Button in backend -->
            <button class="widefat" type="button" id="generate_openai_backend_text">Generate Text (Backend Only)</button>
        </p>
        <div id="openai_backend_output_area" style="display:none;">
            <h4>Generated Text:</h4>
            <textarea id="openai_backend_output" rows="5" readonly></textarea>
        </div>

        <?php
    }

    public function update($new_instance, $old_instance) {
        // Save the widget options
        $instance = array();
        $instance['prompt'] = !empty($new_instance['prompt']) ? sanitize_text_field($new_instance['prompt']) : '';
        $instance['word_limit'] = !empty($new_instance['word_limit']) ? intval($new_instance['word_limit']) : 100;
        return $instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

// Handle the AJAX request for text generation
function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>