diff --git a/admin/form-builder/assets/js/components/field-option-data/index.js b/admin/form-builder/assets/js/components/field-option-data/index.js
index 7212bf8bc..de24f79c2 100644
--- a/admin/form-builder/assets/js/components/field-option-data/index.js
+++ b/admin/form-builder/assets/js/components/field-option-data/index.js
@@ -16,7 +16,13 @@ Vue.component('field-option-data', {
sync_value: true,
options: [],
selected: [],
- display: !this.editing_form_field.hide_option_data // hide this field for the events calendar
+ display: !this.editing_form_field.hide_option_data, // hide this field for the events calendar
+ show_ai_modal: false,
+ show_ai_config_modal: false,
+ ai_prompt: '',
+ ai_loading: false,
+ ai_error: '',
+ ai_generated_options: []
};
},
@@ -27,6 +33,12 @@ Vue.component('field-option-data', {
field_selected: function () {
return this.editing_form_field.selected;
+ },
+
+ all_ai_selected: function () {
+ return this.ai_generated_options.length > 0 && this.ai_generated_options.every(function(opt) {
+ return opt.selected;
+ });
}
},
@@ -97,6 +109,98 @@ Vue.component('field-option-data', {
if (this.sync_value) {
this.options[index].value = label.toLocaleLowerCase().replace( /\s/g, '_' );
}
+ },
+
+ open_ai_modal: function () {
+ // Check if AI is configured
+ if (!wpuf_form_builder.ai_configured) {
+ this.show_ai_config_modal = true;
+ return;
+ }
+ this.show_ai_modal = true;
+ this.ai_prompt = '';
+ this.ai_error = '';
+ this.ai_generated_options = [];
+ },
+
+ close_ai_config_modal: function () {
+ this.show_ai_config_modal = false;
+ },
+
+ go_to_ai_settings: function () {
+ window.location.href = wpuf_form_builder.ai_settings_url;
+ },
+
+ close_ai_modal: function () {
+ this.show_ai_modal = false;
+ this.ai_prompt = '';
+ this.ai_error = '';
+ this.ai_generated_options = [];
+ this.ai_loading = false;
+ },
+
+ generate_ai_options: function () {
+ var self = this;
+
+ if (!this.ai_prompt.trim()) {
+ return;
+ }
+
+ this.ai_loading = true;
+ this.ai_error = '';
+
+ var field_type = this.editing_form_field.template;
+
+ wp.ajax.post('wpuf_ai_generate_field_options', {
+ prompt: this.ai_prompt,
+ field_type: field_type,
+ nonce: wpuf_form_builder.nonce
+ }).done(function(response) {
+ // wp.ajax.post returns data directly in response (not response.data)
+ // when using wp_send_json_success(['options' => $options])
+ var options = response.options || (response.data && response.data.options) || [];
+
+ if (options.length > 0) {
+ var mapped_options = options.map(function(opt) {
+ return {
+ label: opt.label || opt,
+ value: opt.value || opt,
+ selected: true
+ };
+ });
+ self.$set(self, 'ai_generated_options', mapped_options);
+ } else {
+ self.ai_error = response.message || (response.data && response.data.message) || self.i18n.something_went_wrong;
+ }
+ }).fail(function(error) {
+ self.ai_error = error.message || self.i18n.something_went_wrong;
+ }).always(function() {
+ self.ai_loading = false;
+ });
+ },
+
+ select_all_ai_options: function () {
+ var select_state = !this.all_ai_selected;
+ this.ai_generated_options.forEach(function(opt) {
+ opt.selected = select_state;
+ });
+ },
+
+ import_ai_options: function () {
+ var self = this;
+ var selected_options = this.ai_generated_options.filter(function(opt) {
+ return opt.selected;
+ });
+
+ selected_options.forEach(function(opt) {
+ self.options.push({
+ label: opt.label,
+ value: opt.value,
+ id: self.get_random_id()
+ });
+ });
+
+ this.close_ai_modal();
}
},
diff --git a/admin/form-builder/assets/js/components/field-option-data/template.php b/admin/form-builder/assets/js/components/field-option-data/template.php
index dd5b561e8..c9bdd33df 100644
--- a/admin/form-builder/assets/js/components/field-option-data/template.php
+++ b/admin/form-builder/assets/js/components/field-option-data/template.php
@@ -25,7 +25,21 @@ class="!wpuf-mr-2" />
-
+
+
+
+
+
+
+
+
+
+
{{ ai_error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/js-templates/form-components.php b/assets/js-templates/form-components.php
index 7001a5830..d1a8de691 100644
--- a/assets/js-templates/form-components.php
+++ b/assets/js-templates/form-components.php
@@ -410,7 +410,21 @@ class="!wpuf-mr-2" />
-
+
+
+
+
+
+
+
+
+
+
{{ ai_error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/js/wpuf-form-builder-components.js b/assets/js/wpuf-form-builder-components.js
index e223647e8..7c322fd9e 100644
--- a/assets/js/wpuf-form-builder-components.js
+++ b/assets/js/wpuf-form-builder-components.js
@@ -650,7 +650,13 @@ Vue.component('field-option-data', {
sync_value: true,
options: [],
selected: [],
- display: !this.editing_form_field.hide_option_data // hide this field for the events calendar
+ display: !this.editing_form_field.hide_option_data, // hide this field for the events calendar
+ show_ai_modal: false,
+ show_ai_config_modal: false,
+ ai_prompt: '',
+ ai_loading: false,
+ ai_error: '',
+ ai_generated_options: []
};
},
@@ -661,6 +667,12 @@ Vue.component('field-option-data', {
field_selected: function () {
return this.editing_form_field.selected;
+ },
+
+ all_ai_selected: function () {
+ return this.ai_generated_options.length > 0 && this.ai_generated_options.every(function(opt) {
+ return opt.selected;
+ });
}
},
@@ -731,6 +743,98 @@ Vue.component('field-option-data', {
if (this.sync_value) {
this.options[index].value = label.toLocaleLowerCase().replace( /\s/g, '_' );
}
+ },
+
+ open_ai_modal: function () {
+ // Check if AI is configured
+ if (!wpuf_form_builder.ai_configured) {
+ this.show_ai_config_modal = true;
+ return;
+ }
+ this.show_ai_modal = true;
+ this.ai_prompt = '';
+ this.ai_error = '';
+ this.ai_generated_options = [];
+ },
+
+ close_ai_config_modal: function () {
+ this.show_ai_config_modal = false;
+ },
+
+ go_to_ai_settings: function () {
+ window.location.href = wpuf_form_builder.ai_settings_url;
+ },
+
+ close_ai_modal: function () {
+ this.show_ai_modal = false;
+ this.ai_prompt = '';
+ this.ai_error = '';
+ this.ai_generated_options = [];
+ this.ai_loading = false;
+ },
+
+ generate_ai_options: function () {
+ var self = this;
+
+ if (!this.ai_prompt.trim()) {
+ return;
+ }
+
+ this.ai_loading = true;
+ this.ai_error = '';
+
+ var field_type = this.editing_form_field.template;
+
+ wp.ajax.post('wpuf_ai_generate_field_options', {
+ prompt: this.ai_prompt,
+ field_type: field_type,
+ nonce: wpuf_form_builder.nonce
+ }).done(function(response) {
+ // wp.ajax.post returns data directly in response (not response.data)
+ // when using wp_send_json_success(['options' => $options])
+ var options = response.options || (response.data && response.data.options) || [];
+
+ if (options.length > 0) {
+ var mapped_options = options.map(function(opt) {
+ return {
+ label: opt.label || opt,
+ value: opt.value || opt,
+ selected: true
+ };
+ });
+ self.$set(self, 'ai_generated_options', mapped_options);
+ } else {
+ self.ai_error = response.message || (response.data && response.data.message) || self.i18n.something_went_wrong;
+ }
+ }).fail(function(error) {
+ self.ai_error = error.message || self.i18n.something_went_wrong;
+ }).always(function() {
+ self.ai_loading = false;
+ });
+ },
+
+ select_all_ai_options: function () {
+ var select_state = !this.all_ai_selected;
+ this.ai_generated_options.forEach(function(opt) {
+ opt.selected = select_state;
+ });
+ },
+
+ import_ai_options: function () {
+ var self = this;
+ var selected_options = this.ai_generated_options.filter(function(opt) {
+ return opt.selected;
+ });
+
+ selected_options.forEach(function(opt) {
+ self.options.push({
+ label: opt.label,
+ value: opt.value,
+ id: self.get_random_id()
+ });
+ });
+
+ this.close_ai_modal();
}
},
diff --git a/includes/AI/FormGenerator.php b/includes/AI/FormGenerator.php
index b1118d750..49fc70ab1 100644
--- a/includes/AI/FormGenerator.php
+++ b/includes/AI/FormGenerator.php
@@ -887,4 +887,390 @@ public function test_connection() {
];
}
}
+
+ /**
+ * Generate field options using AI
+ *
+ * @since 4.2.2
+ *
+ * @param string $prompt User prompt describing the options to generate
+ * @param array $options Additional options
+ * @return array Generated options
+ */
+ public function generate_field_options($prompt, $options = []) {
+ try {
+ $field_type = $options['field_type'] ?? 'dropdown_field';
+ $output_format = $options['output_format'] ?? 'one_per_line';
+ $tone = $options['tone'] ?? 'casual';
+ $max_options = $options['max_options'] ?? 20;
+
+ // Build system prompt for option generation
+ $system_prompt = $this->get_field_options_system_prompt($field_type, $output_format, $tone, $max_options);
+
+ // Store original provider settings
+ $original_provider = $this->current_provider;
+ $original_model = $this->current_model;
+ $original_api_key = $this->api_key;
+
+ // Call AI provider
+ $ai_response = null;
+ switch ($this->current_provider) {
+ case 'openai':
+ $ai_response = $this->call_openai_for_options($system_prompt, $prompt, $options);
+ break;
+
+ case 'anthropic':
+ $ai_response = $this->call_anthropic_for_options($system_prompt, $prompt, $options);
+ break;
+
+ case 'google':
+ $ai_response = $this->call_google_for_options($system_prompt, $prompt, $options);
+ break;
+
+ default:
+ throw new \Exception('Unsupported AI provider: ' . $this->current_provider);
+ }
+
+ // Restore original provider settings
+ $this->current_provider = $original_provider;
+ $this->current_model = $original_model;
+ $this->api_key = $original_api_key;
+
+ if (isset($ai_response['error']) && $ai_response['error']) {
+ return [
+ 'success' => false,
+ 'error' => true,
+ 'message' => $ai_response['message'] ?? 'Failed to generate options',
+ 'provider' => $this->current_provider
+ ];
+ }
+
+ return [
+ 'success' => true,
+ 'options' => $ai_response['options'] ?? [],
+ 'provider' => $this->current_provider,
+ 'model' => $this->current_model
+ ];
+
+ } catch (\Exception $e) {
+ return [
+ 'success' => false,
+ 'error' => true,
+ 'message' => $e->getMessage(),
+ 'provider' => $this->current_provider
+ ];
+ }
+ }
+
+ /**
+ * Get system prompt for field options generation
+ *
+ * @param string $field_type Field type
+ * @param string $output_format Output format
+ * @param string $tone Tone of options
+ * @param int $max_options Maximum number of options
+ * @return string System prompt
+ */
+ private function get_field_options_system_prompt($field_type, $output_format, $tone, $max_options) {
+ $prompt = "You are an AI assistant helping to generate field options for a WordPress form.\n\n";
+ $prompt .= "**Task:** Generate a list of options based on the user's request.\n\n";
+ $prompt .= "**Field Type:** {$field_type}\n";
+ $prompt .= "**Output Format:** {$output_format}\n";
+ $prompt .= "**Tone:** {$tone}\n";
+ $prompt .= "**Maximum Options:** {$max_options}\n\n";
+
+ $prompt .= "**Requirements:**\n";
+ $prompt .= "1. Generate between 1 and {$max_options} options\n";
+ $prompt .= "2. Each option should be clear, concise, and relevant\n";
+ $prompt .= "3. Options should be appropriate for the specified tone\n";
+ $prompt .= "4. Avoid duplicate or very similar options\n";
+ $prompt .= "5. Use proper capitalization and formatting\n\n";
+
+ if ($output_format === 'value_label') {
+ $prompt .= "**Output Format:** Return a JSON object with 'options' array containing objects with 'value' and 'label' keys.\n";
+ $prompt .= "Example:\n";
+ $prompt .= "{\n";
+ $prompt .= " \"options\": [\n";
+ $prompt .= " {\"value\": \"option_1\", \"label\": \"Option 1\"},\n";
+ $prompt .= " {\"value\": \"option_2\", \"label\": \"Option 2\"}\n";
+ $prompt .= " ]\n";
+ $prompt .= "}\n\n";
+ } else {
+ $prompt .= "**Output Format:** Return a JSON object with 'options' array containing simple strings (one option per item).\n";
+ $prompt .= "Example:\n";
+ $prompt .= "{\n";
+ $prompt .= " \"options\": [\"Option 1\", \"Option 2\", \"Option 3\"]\n";
+ $prompt .= "}\n\n";
+ }
+
+ $prompt .= "**IMPORTANT:** Respond ONLY with valid JSON. Do not include any explanatory text, markdown formatting, or code blocks.\n";
+
+ return $prompt;
+ }
+
+ /**
+ * Call OpenAI for field options generation
+ *
+ * @param string $system_prompt System prompt
+ * @param string $user_prompt User prompt
+ * @param array $options Additional options
+ * @return array Result
+ */
+ private function call_openai_for_options($system_prompt, $user_prompt, $options = []) {
+ $model_config = $this->get_model_config('openai', $this->current_model);
+
+ $body = [
+ 'model' => $this->current_model,
+ 'messages' => [
+ ['role' => 'system', 'content' => $system_prompt],
+ ['role' => 'user', 'content' => $user_prompt]
+ ],
+ 'temperature' => 0.7,
+ 'max_tokens' => 1000
+ ];
+
+ if ($model_config['supports_json_mode']) {
+ $body['response_format'] = ['type' => 'json_object'];
+ }
+
+ $args = [
+ 'method' => 'POST',
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->api_key,
+ 'Content-Type' => 'application/json'
+ ],
+ 'body' => json_encode($body),
+ 'timeout' => 60
+ ];
+
+ $response = wp_safe_remote_request($this->provider_configs['openai']['endpoint'], $args);
+
+ if (is_wp_error($response)) {
+ throw new \Exception('OpenAI API request failed: ' . $response->get_error_message());
+ }
+
+ $status_code = wp_remote_retrieve_response_code($response);
+ if ($status_code !== 200) {
+ $error_body = wp_remote_retrieve_body($response);
+ throw new \Exception("OpenAI API returned HTTP {$status_code}: {$error_body}");
+ }
+
+ $body = wp_remote_retrieve_body($response);
+ $data = json_decode($body, true);
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new \Exception('Invalid JSON response from OpenAI API');
+ }
+
+ if (isset($data['error'])) {
+ throw new \Exception('OpenAI API Error: ' . $data['error']['message']);
+ }
+
+ if (!isset($data['choices'][0]['message']['content'])) {
+ throw new \Exception('Invalid OpenAI response format');
+ }
+
+ return $this->parse_options_response($data['choices'][0]['message']['content'], $options);
+ }
+
+ /**
+ * Call Anthropic for field options generation
+ *
+ * @param string $system_prompt System prompt
+ * @param string $user_prompt User prompt
+ * @param array $options Additional options
+ * @return array Result
+ */
+ private function call_anthropic_for_options($system_prompt, $user_prompt, $options = []) {
+ $body = [
+ 'model' => $this->current_model,
+ 'system' => $system_prompt,
+ 'messages' => [
+ ['role' => 'user', 'content' => $user_prompt]
+ ],
+ 'temperature' => 0.7,
+ 'max_tokens' => 1000
+ ];
+
+ $args = [
+ 'method' => 'POST',
+ 'headers' => [
+ 'x-api-key' => $this->api_key,
+ 'anthropic-version' => '2023-06-01',
+ 'Content-Type' => 'application/json'
+ ],
+ 'body' => json_encode($body),
+ 'timeout' => 60
+ ];
+
+ $response = wp_safe_remote_request($this->provider_configs['anthropic']['endpoint'], $args);
+
+ if (is_wp_error($response)) {
+ throw new \Exception('Anthropic API request failed: ' . $response->get_error_message());
+ }
+
+ $status_code = wp_remote_retrieve_response_code($response);
+ if ($status_code !== 200) {
+ $error_body = wp_remote_retrieve_body($response);
+ throw new \Exception("Anthropic API returned HTTP {$status_code}: {$error_body}");
+ }
+
+ $body = wp_remote_retrieve_body($response);
+ $data = json_decode($body, true);
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new \Exception('Invalid JSON response from Anthropic API');
+ }
+
+ if (isset($data['error'])) {
+ throw new \Exception('Anthropic API Error: ' . $data['error']['message']);
+ }
+
+ if (!isset($data['content'][0]['text'])) {
+ throw new \Exception('Invalid Anthropic response format');
+ }
+
+ return $this->parse_options_response($data['content'][0]['text'], $options);
+ }
+
+ /**
+ * Call Google for field options generation
+ *
+ * @param string $system_prompt System prompt
+ * @param string $user_prompt User prompt
+ * @param array $options Additional options
+ * @return array Result
+ */
+ private function call_google_for_options($system_prompt, $user_prompt, $options = []) {
+ $model_config = $this->get_model_config('google', $this->current_model);
+
+ $endpoint = str_replace('{model}', $this->current_model, $this->provider_configs['google']['endpoint']);
+ $endpoint .= '?key=' . $this->api_key;
+
+ $body = [
+ 'contents' => [
+ [
+ 'parts' => [
+ ['text' => $system_prompt . "\n\nUser request: " . $user_prompt]
+ ]
+ ]
+ ],
+ 'generationConfig' => [
+ 'temperature' => 0.7,
+ 'maxOutputTokens' => 1000
+ ]
+ ];
+
+ if ($model_config['supports_json_mode']) {
+ $body['generationConfig']['responseMimeType'] = 'application/json';
+ }
+
+ $args = [
+ 'method' => 'POST',
+ 'headers' => [
+ 'Content-Type' => 'application/json'
+ ],
+ 'body' => json_encode($body),
+ 'timeout' => 60
+ ];
+
+ $response = wp_safe_remote_request($endpoint, $args);
+
+ if (is_wp_error($response)) {
+ throw new \Exception('Google API request failed: ' . $response->get_error_message());
+ }
+
+ $status_code = wp_remote_retrieve_response_code($response);
+ if ($status_code !== 200) {
+ $error_body = wp_remote_retrieve_body($response);
+ throw new \Exception("Google API returned HTTP {$status_code}: {$error_body}");
+ }
+
+ $body = wp_remote_retrieve_body($response);
+ $data = json_decode($body, true);
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new \Exception('Invalid JSON response from Google API');
+ }
+
+ if (isset($data['error'])) {
+ throw new \Exception('Google API Error: ' . ($data['error']['message'] ?? 'Unknown error'));
+ }
+
+ if (!isset($data['candidates'][0]['content']['parts'][0]['text'])) {
+ throw new \Exception('Invalid Google response format');
+ }
+
+ return $this->parse_options_response($data['candidates'][0]['content']['parts'][0]['text'], $options);
+ }
+
+ /**
+ * Parse options from AI response
+ *
+ * @param string $content AI response content
+ * @param array $options Request options
+ * @return array Parsed options
+ */
+ private function parse_options_response($content, $options = []) {
+ // Clean and extract JSON from the response
+ $json_content = trim($content);
+
+ // Remove any markdown code blocks if present
+ $json_content = preg_replace('/^```(?:json)?\s*|\s*```$/m', '', $json_content);
+
+ // Remove any text before the first { or after the last }
+ $json_content = preg_replace('/^[^{]*/', '', $json_content);
+ $json_content = preg_replace('/[^}]*$/', '', $json_content);
+
+ // Try to find the JSON object
+ $start = strpos($json_content, '{');
+ $end = strrpos($json_content, '}');
+
+ if ($start !== false && $end !== false && $end > $start) {
+ $json_content = substr($json_content, $start, $end - $start + 1);
+ }
+
+ // Attempt to decode JSON
+ $parsed = json_decode($json_content, true);
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ return [
+ 'error' => true,
+ 'message' => 'Unable to parse AI response. Please try again.'
+ ];
+ }
+
+ // Extract options from parsed response
+ $field_options = [];
+ $output_format = $options['output_format'] ?? 'one_per_line';
+
+ if (isset($parsed['options']) && is_array($parsed['options'])) {
+ if ($output_format === 'value_label') {
+ // Expecting array of objects with 'value' and 'label'
+ foreach ($parsed['options'] as $option) {
+ if (is_array($option) && isset($option['value']) && isset($option['label'])) {
+ $field_options[] = [
+ 'label' => $option['label'],
+ 'value' => $option['value']
+ ];
+ }
+ }
+ } else {
+ // Expecting array of strings (one per line)
+ foreach ($parsed['options'] as $index => $option) {
+ if (is_string($option)) {
+ $field_options[] = [
+ 'label' => $option,
+ 'value' => sanitize_title( $option )
+ ];
+ }
+ }
+ }
+ }
+
+ return [
+ 'error' => false,
+ 'options' => $field_options
+ ];
+ }
}
diff --git a/includes/AI/RestController.php b/includes/AI/RestController.php
index 1a1300f96..14ee5aa48 100644
--- a/includes/AI/RestController.php
+++ b/includes/AI/RestController.php
@@ -59,6 +59,7 @@ class RestController extends WP_REST_Controller {
*/
public function __construct() {
$this->form_generator = new FormGenerator();
+ add_action( 'wp_ajax_wpuf_ai_generate_field_options', [ $this, 'ajax_generate_field_options' ] );
}
/**
@@ -238,6 +239,48 @@ public function register_routes() {
'callback' => [$this, 'get_models'],
'permission_callback' => [$this, 'check_permission']
]);
+
+ // Generate field options endpoint
+ register_rest_route($this->namespace, '/' . $this->rest_base . '/generate-options', [
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => [$this, 'generate_field_options'],
+ 'permission_callback' => [$this, 'check_permission'],
+ 'args' => [
+ 'prompt' => [
+ 'required' => true,
+ 'type' => 'string',
+ 'sanitize_callback' => 'sanitize_textarea_field',
+ 'validate_callback' => [$this, 'validate_prompt']
+ ],
+ 'field_type' => [
+ 'required' => true,
+ 'type' => 'string',
+ 'enum' => ['dropdown_field', 'radio_field', 'checkbox_field', 'multiple_select'],
+ 'sanitize_callback' => 'sanitize_text_field'
+ ],
+ 'output_format' => [
+ 'required' => false,
+ 'type' => 'string',
+ 'enum' => ['one_per_line', 'value_label'],
+ 'default' => 'one_per_line',
+ 'sanitize_callback' => 'sanitize_text_field'
+ ],
+ 'tone' => [
+ 'required' => false,
+ 'type' => 'string',
+ 'enum' => ['casual', 'formal', 'professional', 'friendly'],
+ 'default' => 'casual',
+ 'sanitize_callback' => 'sanitize_text_field'
+ ],
+ 'max_options' => [
+ 'required' => false,
+ 'type' => 'integer',
+ 'minimum' => 1,
+ 'maximum' => 100,
+ 'default' => 20
+ ]
+ ]
+ ]);
}
/**
@@ -583,6 +626,123 @@ public function get_models(WP_REST_Request $request) {
], 200);
}
+ /**
+ * Generate field options using AI
+ *
+ * @param WP_REST_Request $request REST request object
+ * @return WP_REST_Response|WP_Error Response object
+ */
+ public function generate_field_options(WP_REST_Request $request) {
+ try {
+ $prompt = $request->get_param('prompt');
+ $field_type = $request->get_param('field_type');
+ $output_format = $request->get_param('output_format') ?? 'one_per_line';
+ $tone = $request->get_param('tone') ?? 'casual';
+ $max_options = $request->get_param('max_options') ?? 20;
+
+ // Validate max options
+ if ($max_options < 1 || $max_options > 100) {
+ return new WP_Error(
+ 'invalid_max_options',
+ __('Maximum options must be between 1 and 100', 'wp-user-frontend'),
+ ['status' => 400]
+ );
+ }
+
+ // Call FormGenerator to generate options
+ $result = $this->form_generator->generate_field_options($prompt, [
+ 'field_type' => $field_type,
+ 'output_format' => $output_format,
+ 'tone' => $tone,
+ 'max_options' => $max_options
+ ]);
+
+ if (isset($result['error']) && $result['error']) {
+ return new WP_Error(
+ 'generation_failed',
+ $result['message'] ?? __('Failed to generate field options', 'wp-user-frontend'),
+ ['status' => 400]
+ );
+ }
+
+ // Sanitize generated options
+ $sanitized_options = $this->sanitize_field_options($result['options'] ?? []);
+
+ return new WP_REST_Response([
+ 'success' => true,
+ 'options' => $sanitized_options,
+ 'message' => __('Options generated successfully', 'wp-user-frontend')
+ ], 200);
+
+ } catch (\Exception $e) {
+ return new WP_Error(
+ 'generation_error',
+ __('An error occurred while generating options. Please try again.', 'wp-user-frontend'),
+ ['status' => 500]
+ );
+ }
+ }
+
+ /**
+ * Sanitize field options
+ *
+ * @param array $options Raw options from AI
+ * @return array Sanitized options
+ */
+ private function sanitize_field_options($options) {
+ if (!is_array($options)) {
+ return [];
+ }
+
+ $sanitized = [];
+ foreach ($options as $key => $value) {
+ // Generate safe key from value if numeric key
+ if (is_numeric($key)) {
+ $safe_key = sanitize_key(strtolower(str_replace(' ', '_', $value)));
+ } else {
+ $safe_key = sanitize_key($key);
+ }
+
+ $sanitized[$safe_key] = sanitize_text_field($value);
+ }
+
+ return $sanitized;
+ }
+
+ /**
+ * AJAX handler for generating field options
+ *
+ * @since 4.2.2
+ *
+ * @return void
+ */
+ public function ajax_generate_field_options() {
+ check_ajax_referer( 'form-builder-setting-nonce', 'nonce' );
+
+ if ( ! $this->check_permission() ) {
+ wp_send_json_error( [ 'message' => __( 'Permission denied', 'wp-user-frontend' ) ] );
+ }
+
+ $prompt = sanitize_textarea_field( $_POST['prompt'] ?? '' );
+ $field_type = sanitize_text_field( $_POST['field_type'] ?? 'dropdown_field' );
+
+ if ( empty( $prompt ) ) {
+ wp_send_json_error( [ 'message' => __( 'Prompt is required', 'wp-user-frontend' ) ] );
+ }
+
+ $result = $this->form_generator->generate_field_options( $prompt, [ 'field_type' => $field_type ] );
+
+ if ( is_wp_error( $result ) ) {
+ wp_send_json_error( [ 'message' => $result->get_error_message() ] );
+ }
+
+ if ( isset( $result['success'] ) && $result['success'] ) {
+ wp_send_json_success( [ 'options' => $result['options'] ] );
+ }
+
+ wp_send_json_error( [ 'message' => $result['message'] ?? __( 'Failed to generate options', 'wp-user-frontend' ) ] );
+ }
+
/**
* Check if user has permission to use AI form builder
*
diff --git a/includes/Admin/Forms/Admin_Form_Builder.php b/includes/Admin/Forms/Admin_Form_Builder.php
index fdb827e16..87aee3c05 100644
--- a/includes/Admin/Forms/Admin_Form_Builder.php
+++ b/includes/Admin/Forms/Admin_Form_Builder.php
@@ -178,6 +178,15 @@ public function admin_enqueue_scripts() {
// Load icon configuration
$icon_config = $this->get_icon_config();
+ // Check AI configuration directly
+ $ai_settings = get_option( 'wpuf_ai', [] );
+ $ai_provider = isset( $ai_settings['ai_provider'] ) ? $ai_settings['ai_provider'] : '';
+ $ai_model = isset( $ai_settings['ai_model'] ) ? $ai_settings['ai_model'] : '';
+ $provider_key = $ai_provider . '_api_key';
+ $ai_api_key = isset( $ai_settings[ $provider_key ] ) ? $ai_settings[ $provider_key ] : '';
+ $ai_configured = ! empty( $ai_provider ) && ! empty( $ai_api_key ) && ! empty( $ai_model );
+ $ai_settings_url = admin_url( 'admin.php?page=wpuf-settings#/ai' );
+
$wpuf_form_builder = apply_filters(
'wpuf_form_builder_localize_script',
[
@@ -208,6 +217,8 @@ public function admin_enqueue_scripts() {
'free_icon' => $free_icon,
'icons' => $icon_config['icons'],
'defaultIcons' => $icon_config['defaultIcons'],
+ 'ai_configured' => $ai_configured,
+ 'ai_settings_url' => $ai_settings_url,
]
);
$wpuf_form_builder = wpuf_unset_conditional( $wpuf_form_builder );
diff --git a/package.json b/package.json
index dc5222a19..22cca4c4a 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"build:subscriptions": "ENTRY=subscriptions vite build",
"build:frontend-subscriptions": "ENTRY=frontend-subscriptions vite build",
"build:ai-form-builder": "ENTRY=ai-form-builder vite build",
- "build:css": "grunt tailwind-minify"
+ "build:css": "grunt tailwind && grunt tailwind-minify"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.7",
diff --git a/src/css/admin/form-builder.css b/src/css/admin/form-builder.css
index 22d77e729..2c1dec38a 100644
--- a/src/css/admin/form-builder.css
+++ b/src/css/admin/form-builder.css
@@ -317,3 +317,39 @@ button.swal2-cancel.swal2-styled.swal2-default-outline {
.wpuf-pro-modal-right img {
@apply wpuf-w-auto wpuf-h-80 wpuf-rounded wpuf-block;
}
+
+/* AI Field Options Modal */
+.wpuf-ai-modal-overlay {
+ @apply wpuf-fixed wpuf-inset-0 wpuf-bg-black wpuf-bg-opacity-50 wpuf-flex wpuf-items-center wpuf-justify-center wpuf-z-50;
+}
+
+.wpuf-ai-modal {
+ @apply wpuf-bg-white wpuf-rounded-lg wpuf-shadow-xl wpuf-max-w-2xl wpuf-w-full wpuf-mx-4;
+ max-height: 90vh;
+}
+
+.wpuf-ai-modal-header {
+ @apply wpuf-flex wpuf-justify-between wpuf-items-center wpuf-p-6 wpuf-border-b;
+}
+
+.wpuf-ai-modal-header h3 {
+ @apply wpuf-text-lg wpuf-font-semibold wpuf-m-0;
+}
+
+.wpuf-ai-modal-close {
+ @apply wpuf-text-gray-400 hover:wpuf-text-gray-600 wpuf-text-3xl wpuf-leading-none wpuf-bg-transparent wpuf-border-0 wpuf-cursor-pointer;
+}
+
+.wpuf-ai-modal-body {
+ @apply wpuf-p-6;
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+.wpuf-ai-modal-footer {
+ @apply wpuf-flex wpuf-justify-end wpuf-gap-3 wpuf-p-6 wpuf-border-t;
+}
+
+.wpuf-ai-options-list {
+ @apply wpuf-max-h-64 wpuf-overflow-y-auto wpuf-border wpuf-rounded wpuf-p-3;
+}