-
Notifications
You must be signed in to change notification settings - Fork 153
Add AI-powered field options generator for dropdown, radio, and checkbox fields #1750
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Add AI-powered field options generator for dropdown, radio, and checkbox fields #1750
Conversation
WalkthroughThis PR introduces AI-assisted option generation for form fields, adding frontend modals and state management, backend API endpoints, AI provider integrations (OpenAI, Anthropic, Google), and corresponding CSS styling to enable users to generate form field options via AI through the form builder interface. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (6)
assets/js-templates/form-components.php (1)
413-427: AI options UI is well-integrated; consider de-duplicating modal markupThe AI trigger button and both modals are wired correctly into the existing option-data template (state flags, handlers, and Tailwind utility classes all line up with the new CSS and backend endpoints).
The same AI header + modal blocks now exist here and in
admin/form-builder/assets/js/components/field-option-data/template.php, which will be easy to accidentally diverge over time. If you keep iterating on this feature, consider extracting the shared modal markup into a single PHP partial or Vue sub-template and including it from both places to reduce duplication.Also applies to: 510-603
admin/form-builder/assets/js/components/field-option-data/template.php (1)
28-42: Keep this AI modal template in sync with the global form-components templateThis component template’s AI button and modals are consistent with the markup added to
assets/js-templates/form-components.phpand should work fine with the shared JS logic and CSS.Because the two files now contain nearly identical AI sections, any future copy or behavior tweak will have to be applied in both places. If this pattern solidifies, consider extracting the AI options modal into a shared partial or Vue sub-template and including it here and in
form-components.phpto avoid drift.Also applies to: 125-218
assets/js/wpuf-form-builder-components.js (1)
816-821: Vue 2 reactivity issue when toggling selection.Directly mutating
opt.selectedon array items may not trigger reactivity in Vue 2. Consider usingthis.$set()for each item to ensure the UI updates correctly.Apply this diff:
select_all_ai_options: function () { var select_state = !this.all_ai_selected; + var self = this; - this.ai_generated_options.forEach(function(opt) { - opt.selected = select_state; + this.ai_generated_options.forEach(function(opt, index) { + self.$set(self.ai_generated_options[index], 'selected', select_state); }); },admin/form-builder/assets/js/components/field-option-data/index.js (1)
182-187: Same Vue 2 reactivity concern as in bundled file.Direct property mutation on array items may not trigger Vue 2 reactivity. Use
this.$set()for reliable updates.Apply this diff:
select_all_ai_options: function () { var select_state = !this.all_ai_selected; + var self = this; - this.ai_generated_options.forEach(function(opt) { - opt.selected = select_state; + this.ai_generated_options.forEach(function(opt, index) { + self.$set(self.ai_generated_options[index], 'selected', select_state); }); },includes/AI/FormGenerator.php (2)
1258-1268: Remove unused$indexvariable.The static analysis correctly identified that
$indexis unused in the foreach loop.Apply this diff:
} else { // Expecting array of strings (one per line) - foreach ($parsed['options'] as $index => $option) { + foreach ($parsed['options'] as $option) { if (is_string($option)) { $field_options[] = [ 'label' => $option, 'value' => sanitize_title( $option ) ]; } } }
1019-1074: Consider using model-specific token parameter.The method hardcodes
max_tokensto 1000, butget_model_configis called without using itstoken_paramconfiguration. For consistency withgenerate_form, consider using the model config.$body = [ 'model' => $this->current_model, 'messages' => [ ['role' => 'system', 'content' => $system_prompt], ['role' => 'user', 'content' => $user_prompt] ], - 'temperature' => 0.7, - 'max_tokens' => 1000 + 'temperature' => 0.7 ]; + // Set token parameter based on model + if ($model_config['token_location'] === 'body') { + $body[$model_config['token_param']] = 1000; + } + if ($model_config['supports_json_mode']) { $body['response_format'] = ['type' => 'json_object']; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
admin/form-builder/assets/js/components/field-option-data/index.js(3 hunks)admin/form-builder/assets/js/components/field-option-data/template.php(2 hunks)assets/js-templates/form-components.php(2 hunks)assets/js/wpuf-form-builder-components.js(3 hunks)includes/AI/FormGenerator.php(1 hunks)includes/AI/RestController.php(3 hunks)includes/Admin/Forms/Admin_Form_Builder.php(2 hunks)src/css/admin/form-builder.css(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
includes/Admin/Forms/Admin_Form_Builder.php (1)
Lib/WeDevs_Settings_API.php (1)
get_option(616-625)
includes/AI/RestController.php (1)
includes/AI/FormGenerator.php (1)
generate_field_options(900-963)
admin/form-builder/assets/js/components/field-option-data/index.js (1)
assets/js/subscriptions.min.js (1)
wp(21-21)
🪛 Biome (2.1.2)
src/css/admin/form-builder.css
[error] 355-355: expected } but instead the file ends
the file ends here
(parse)
🪛 PHPMD (2.15.0)
includes/AI/FormGenerator.php
1260-1260: Avoid unused local variables such as '$index'. (undefined)
(UnusedLocalVariable)
🔇 Additional comments (8)
src/css/admin/form-builder.css (1)
321-355: The CSS file is correctly structured with balanced braces—no action neededVerification confirms that the file has 79 matching opening and closing braces, and properly ends with a closing brace after the
.wpuf-ai-options-listrule. The new AI modal styles (lines 321–355) are all well-formed. The Biome parse error reported is either a false positive or was resolved. No fixes are required.assets/js/wpuf-form-builder-components.js (2)
653-660: AI state initialization looks good.The new AI-related state properties are properly initialized with appropriate default values.
672-676: LGTM on computed property.The
all_ai_selectedcomputed correctly checks if all AI-generated options are selected.admin/form-builder/assets/js/components/field-option-data/index.js (2)
19-26: AI state initialization is consistent.The AI-related state properties are properly initialized and match the bundled component.
154-157: Nonce validation is correctly implemented server-side.The AJAX handler at
includes/AI/RestController.php:720properly validates the nonce usingcheck_ajax_referer( 'form-builder-setting-nonce', 'nonce' ), and the nonce is created withwp_create_nonce( 'form-builder-setting-nonce' )in the form builder localization data. The action name and parameter name match correctly between client and server.includes/AI/FormGenerator.php (3)
900-963: Good structure for the new AI options generation method.The method properly saves and restores provider settings, handles errors gracefully, and delegates to provider-specific implementations. The try-catch ensures consistent error responses.
974-1009: System prompt is well-structured.The prompt clearly defines the task, output format, and constraints. The max_options limit is properly communicated to the AI.
1214-1241: JSON parsing handles edge cases well.The
parse_options_responsemethod properly handles markdown code blocks, extracts JSON from mixed content, and returns structured error responses on parse failures.
| 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; | ||
| }); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Consider sanitizing the prompt before sending.
The ai_prompt is sent directly to the server. While server-side sanitization should handle this, trimming and basic validation on the client side provides defense in depth and better UX.
Additionally, ensure the i18n.something_went_wrong key exists in your localization object to avoid showing undefined on errors.
🏁 Script executed:
#!/bin/bash
# Check if something_went_wrong is defined in localization
rg -n "something_went_wrong" --type=phpLength of output: 0
I need to broaden the search to find where the i18n localization object is defined. Let me check the JavaScript codebase and look for how i18n.something_went_wrong is populated.
---
🏁 Script executed:
```shell
#!/bin/bash
# Search for i18n definitions and something_went_wrong across all file types
rg -n "something_went_wrong" -g "*.js" -g "*.php" -C 3
Length of output: 2146
🏁 Script executed:
#!/bin/bash
# Look for i18n object assignments/initialization in JS
rg -n "i18n\s*[=:]" --type=js -C 2Length of output: 1520
🏁 Script executed:
#!/bin/bash
# Check the specific file for i18n initialization
grep -n "i18n" assets/js/wpuf-form-builder-components.js | head -20Length of output: 1507
I need to find where wpuf_form_builder.i18n is populated via PHP localization. Let me search for the wp_localize_script calls.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find where wpuf_form_builder is localized
rg -n "wp_localize_script.*wpuf_form_builder|wpuf_form_builder.*wp_localize_script" --type=php -C 3
Length of output: 665
🏁 Script executed:
#!/bin/bash
# Search for i18n array definitions in PHP files
rg -n "i18n.*=.*array|'i18n'\s*=>" --type=php -C 5Length of output: 3984
🏁 Script executed:
#!/bin/bash
# Search specifically for something_went_wrong in all file types (case-insensitive)
rg -in "something_went_wrong"Length of output: 659
I need to find the i18n() method that defines the localization keys. Let me search for it.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find the i18n() method definition in Admin_Form_Builder
rg -n "function i18n|public function i18n" includes/Admin/Forms/Admin_Form_Builder.php -A 30
Length of output: 2412
🏁 Script executed:
#!/bin/bash
# Search for any AI-related i18n keys in the file
rg -n "ai_error|ai_generated|ai_" includes/Admin/Forms/Admin_Form_Builder.php --type=phpLength of output: 719
Let me get the complete i18n() method and check how ai_prompt is handled:
---
🏁 Script executed:
```shell
#!/bin/bash
# Get full i18n() method
rg -n "private function i18n" includes/Admin/Forms/Admin_Form_Builder.php -A 100 | head -150
Length of output: 5985
🏁 Script executed:
#!/bin/bash
# Check how ai_prompt is defined and used in JS
rg -n "ai_prompt" assets/js/wpuf-form-builder-components.js -B 2 -A 2Length of output: 918
Add something_went_wrong to the i18n localization and trim the ai_prompt before sending.
-
The
i18n.something_went_wrongkey is not defined in the localization array returned by thei18n()method inincludes/Admin/Forms/Admin_Form_Builder.php(lines 330–363). This will result inundefinedbeing displayed in error messages at lines 807 and 810. Add the key to the localization array. -
The
ai_promptvalidation at line 779 checksif (!this.ai_prompt.trim())but does not actually trim the value before sending it at line 789. Trim the prompt before posting:prompt: this.ai_prompt.trim().
🤖 Prompt for AI Agents
In assets/js/wpuf-form-builder-components.js around lines 788–814: the code uses
i18n.something_went_wrong (lines ~807 and ~810) which isn’t defined in the PHP
localization array, and it sends this.ai_prompt without trimming it; to fix, add
"something_went_wrong" to the i18n localization array returned by
includes/Admin/Forms/Admin_Form_Builder.php (lines ~330–363) with an appropriate
message, and change the AJAX payload to send the trimmed prompt (use
this.ai_prompt.trim()) so the posted prompt matches the validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align ai_configured detection with stored wpuf_ai option structure
Right now ai_api_key is derived only from $ai_settings[ $ai_provider . '_api_key' ], but includes/AI/RestController::save_settings() persists a generic ai_api_key key into the same wpuf_ai option. If only ai_api_key is set (which is the default behavior), ai_configured will be false and the UI will show “AI not configured” even though the provider is usable.
Consider falling back to ai_api_key when the provider‑specific key is missing so both paths stay in sync:
- $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_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_settings['ai_api_key'] ?? '' );This keeps the new ai_configured flag accurate regardless of whether keys are stored per‑provider or generically.
Also applies to: 220-221
| } catch (\Exception $e) { | ||
| return [ | ||
| 'success' => false, | ||
| 'error' => true, | ||
| 'message' => $e->getMessage(), | ||
| 'provider' => $this->current_provider | ||
| ]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exception handler doesn't restore provider settings.
If an exception occurs, the original provider settings are not restored because the restoration code (lines 935-937) is only reached on success. Consider using a finally-like pattern.
Apply this diff to ensure settings are always restored:
} catch (\Exception $e) {
+ // Restore original provider settings
+ $this->current_provider = $original_provider;
+ $this->current_model = $original_model;
+ $this->api_key = $original_api_key;
+
return [
'success' => false,
'error' => true,
'message' => $e->getMessage(),
'provider' => $this->current_provider
];
}🤖 Prompt for AI Agents
In includes/AI/FormGenerator.php around lines 935 to 962, the catch block at
955–962 returns on exception without restoring the original provider settings
(restoration currently only runs on success at lines 935–937); update the
control flow so the provider settings are always restored—either move the
restoration logic into a finally-style block that executes after the try/catch
or call the same restore routine at the start of the catch before returning,
ensuring the original provider and any related state are set back regardless of
success or failure.
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
API key exposed in URL query string.
While this is Google's documented approach for server-side calls, API keys in URLs can appear in server logs. Consider if there are alternative authentication methods or ensure server logs are properly secured.
🌐 Web query:
Google Gemini API authentication methods 2024
💡 Result:
Summary — Gemini API authentication methods (as of 2024, and still reflected in current docs):
- API key (x-goog-api-key) — primary/simple method for the Google AI Studio / Generative Language endpoints. Best for server-side testing; include in request header. (ai.google.dev)
- OAuth 2.0 — supported for apps requiring delegated, user-scoped access and stricter controls. (ai.google.dev)
- Service accounts / Application Default Credentials (ADC) — used for server-to-server and production use on Google Cloud (Vertex AI / Gemini Enterprise) via ADC / service-account tokens. (cloud.google.com)
- Ephemeral tokens — for direct client-side access to the Live API (short-lived tokens to reduce risk). (ai.google.dev)
Note: API-key support differs by endpoint — Generative Language (ai.google.dev / generativelanguage.googleapis.com) accepts API keys; many Google Cloud Vertex AI endpoints typically expect ADC/service-account or OAuth tokens (API-key behavior varies and can be rejected by some Vertex endpoints). Test the specific endpoint you use. (ai.google.dev)
If you want, I can paste short curl examples for each method.
Citations:
- 1: https://ai.google.dev/api?utm_source=openai
- 2: https://ai.google.dev/gemini-api/docs/oauth?utm_source=openai
- 3: https://cloud.google.com/agentspace/agentspace-enterprise/docs/authentication?utm_source=openai
- 4: https://ai.google.dev/tutorials/setup?utm_source=openai
- 5: https://ai.google.dev/api?utm_source=openai
Implement recommended Google Gemini API authentication using request headers instead of URL parameters.
Google's documented approach for Generative Language endpoints uses the API key in the request header (x-goog-api-key), not as a URL query parameter. The current implementation exposes the API key in the endpoint URL (?key=...), which can appear in server logs, proxy logs, and browser history.
Update the code to pass the API key via the request header instead of appending it to the endpoint URL. This requires modifying both the endpoint construction and the HTTP request logic to include the appropriate header authentication.
🤖 Prompt for AI Agents
In includes/AI/FormGenerator.php around lines 1144 to 1148, the code appends the
API key to the endpoint URL (?key=...), which exposes the key; remove the query
parameter from the constructed endpoint and instead add the API key as the
x-goog-api-key request header when performing the HTTP call; update the HTTP
request logic that sends the request for Google Gemini (curl/stream
context/Guzzle) to include 'x-goog-api-key: {your_api_key}' in headers, and
ensure no other code logs or echoes the API key.
| add_action( 'wp_ajax_wpuf_ai_generate_field_options', [ $this, 'ajax_generate_field_options' ] ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unify REST and AJAX behaviors for AI field options and harden error handling
The new REST route and AJAX action for generating field options are well-scoped (cap checks, nonce check, bounded max_options) and the REST path correctly normalizes options via sanitize_field_options().
Two small follow-ups would make this more robust and consistent:
- Reuse
sanitize_field_options()for AJAX responses
Right now the REST path returns sanitized key => label pairs, while the AJAX path returns raw $result['options']:
// REST
$sanitized_options = $this->sanitize_field_options( $result['options'] ?? [] );
return new WP_REST_Response( [ 'options' => $sanitized_options, ... ] );
// AJAX
if ( isset( $result['success'] ) && $result['success'] ) {
wp_send_json_success( [ 'options' => $result['options'] ] );
}For consistent consumer behavior (and a safer baseline if AI ever emits unexpected content), consider running the AJAX result through the same sanitizer:
- if ( isset( $result['success'] ) && $result['success'] ) {
- wp_send_json_success( [ 'options' => $result['options'] ] );
- }
+ if ( isset( $result['success'] ) && $result['success'] ) {
+ $options = $this->sanitize_field_options( $result['options'] ?? [] );
+ wp_send_json_success( [ 'options' => $options ] );
+ }- Also treat
success === falseas an error in REST handler
Per the FormGenerator::generate_field_options() doc, some error paths may only set 'success' => false without 'error' => true. To avoid returning a “success” payload with an empty options array and a success message in those cases, you could broaden the check:
- if ( isset( $result['error'] ) && $result['error'] ) {
+ if ( ( isset( $result['error'] ) && $result['error'] )
+ || ( isset( $result['success'] ) && ! $result['success'] ) ) {
return new WP_Error(
'generation_failed',
$result['message'] ?? __( 'Failed to generate field options', 'wp-user-frontend' ),
[ 'status' => 400 ]
);
}These tweaks keep both entry points aligned and ensure callers never get a “success” response for a failed generation.
Also applies to: 243-283, 629-745
🤖 Prompt for AI Agents
In includes/AI/RestController.php around lines 62-63, the AJAX handler returns
raw $result['options'] while the REST route sanitizes options via
sanitize_field_options(), and the REST handler only treats error when 'error'
=== true but some failures set 'success' => false; update the AJAX success
branch to pass $result['options'] (or [] if missing) through
$this->sanitize_field_options() before wp_send_json_success, and adjust the REST
handler error check to treat both 'error' === true OR 'success' === false as
failure so it returns an error response instead of a success payload; apply the
same sanitization + broadened error-checking fixes to the other affected ranges
noted (lines 243-283 and 629-745).
feature/ai-field-options-generator Close issue
Add AI-Powered Field Options Generator for Dropdown, Radio, and Checkbox Fields
This branch introduces an AI-powered option generator within the form builder, allowing users to instantly generate field options by describing what they need (e.g., “List of US states”, “Startup categories”, “Common food allergens”). The AI returns a clean, structured list that can be reviewed, selected, and imported into the field with one click.
Key Changes
Backend (PHP)
wpuf_ai_generate_field_optionsinRestController.phpFormGenerator.phpAdmin_Form_Builder.php)Frontend (Vue 2)
Added a dedicated AI Options Modal to the
field-option-datacomponentImplemented proper Vue 2 reactivity using
$set()to update dynamic listsAdded fallback modal for AI Provider Not Configured
Improved AJAX handling (loading, success, error states)
UI / UX Enhancements
builder_class_names()for consistent WP User Frontend designAsset Build & Packaging
This feature delivers a smooth, modern AI workflow inside the WP User Frontend form builder, reducing manual effort and improving usability—especially for fields requiring long or complex option lists.
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.