Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 218 additions & 16 deletions includes/AI/FormGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@
*/
private function load_settings() {
// Get settings from WPUF settings system
$settings = get_option('wpuf_ai', []);

Check failure on line 71 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Expected 1 spaces before closing parenthesis; 0 found

Check failure on line 71 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Expected 1 spaces after opening parenthesis; 0 found

// Get individual settings
$this->current_provider = $settings['ai_provider'] ?? 'openai';

Check failure on line 74 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

null coalescing operator (??) is not present in PHP version 5.6 or earlier
$this->current_model = $settings['ai_model'] ?? 'gpt-3.5-turbo';

Check failure on line 75 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

null coalescing operator (??) is not present in PHP version 5.6 or earlier

// Get provider-specific API key
$provider_key = $this->current_provider . '_api_key';
$this->api_key = $settings[$provider_key] ?? '';

Check failure on line 79 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

null coalescing operator (??) is not present in PHP version 5.6 or earlier

Check failure on line 79 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Array keys must be surrounded by spaces unless they contain a string or an integer.
}

/**
Expand All @@ -88,26 +88,33 @@
* @param array $options Additional options including conversation context
* @return array Generated form data
*/
public function generate_form($prompt, $options = []) {

Check failure on line 91 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Expected 1 spaces before closing parenthesis; 0 found

Check failure on line 91 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Expected 1 spaces after opening parenthesis; 0 found
try {
// Store original provider, model, and API key for restoration
$original_provider = $this->current_provider;
$original_model = $this->current_model;
$original_api_key = $this->api_key;

// Load settings for defaults
$settings = get_option('wpuf_ai', []);

Check failure on line 99 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Expected 1 spaces before closing parenthesis; 0 found

Check failure on line 99 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Expected 1 spaces after opening parenthesis; 0 found

Comment on lines +98 to +100
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clamp temperature server‑side and address minor coding‑standards issues

  • You correctly default temperature from wpuf_ai settings, but it’s only floatval()’d here. Consider clamping to [0.0, 1.0] at this call site as well (not just in the settings UI) so any malformed stored value can’t leak through to the provider APIs.
  • PHPCS hint: change get_option('wpuf_ai', []); to WordPress style with spaces: get_option( 'wpuf_ai', [] );.

Also applies to: 113-116

🧰 Tools
🪛 GitHub Check: Run PHPCS inspection

[failure] 99-99:
Expected 1 spaces before closing parenthesis; 0 found


[failure] 99-99:
Expected 1 spaces after opening parenthesis; 0 found

🤖 Prompt for AI Agents
In includes/AI/FormGenerator.php around lines 98-100 (and similarly at 113-116)
the call to get_option should use WordPress spacing and the retrieved
temperature must be clamped server-side; change get_option('wpuf_ai', []); to
get_option( 'wpuf_ai', [] ); then when reading the temperature, cast to float
and clamp it into the range 0.0–1.0 (e.g. use min(max(floatval(...), 0.0), 1.0))
before using or passing it to provider APIs so malformed stored values cannot
leak through.

// Apply per-request overrides if provided
if ( isset($options['provider']) && ! empty($options['provider']) ) {
$this->current_provider = $options['provider'];

// Update API key to match the new provider
$settings = get_option('wpuf_ai', []);
$provider_key = $this->current_provider . '_api_key';
$this->api_key = $settings[$provider_key] ?? '';
}
if ( isset($options['model']) && ! empty($options['model']) ) {
$this->current_model = $options['model'];
}

// Set default temperature from settings if not provided in options
if ( ! isset($options['temperature']) ) {
$options['temperature'] = isset($settings['temperature']) ? floatval($settings['temperature']) : 0.7;
}

// All prompts now go through AI providers

// Using direct API implementation for AI providers
Expand Down Expand Up @@ -228,8 +235,8 @@
$body = [
'model' => $this->current_model,
'messages' => [
['role' => 'system', 'content' => $system_prompt],

Check warning on line 238 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

When a multi-item array uses associative keys, each value should start on a new line.
['role' => 'user', 'content' => $prompt]

Check warning on line 239 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

When a multi-item array uses associative keys, each value should start on a new line.
]
];

Expand Down Expand Up @@ -267,7 +274,7 @@
'Authorization' => 'Bearer ' . $this->api_key,
'Content-Type' => 'application/json'
],
'body' => json_encode($body),

Check warning on line 277 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

json_encode() is discouraged. Use wp_json_encode() instead.
'timeout' => 120
];

Expand Down Expand Up @@ -303,7 +310,7 @@
}

if (!isset($data['choices'][0]['message']['content'])) {
throw new \Exception('Invalid OpenAI response format. Response: ' . json_encode($data));

Check warning on line 313 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

json_encode() is discouraged. Use wp_json_encode() instead.
}

$content = $data['choices'][0]['message']['content'];
Expand Down Expand Up @@ -415,7 +422,7 @@
'model' => $this->current_model,
'system' => $system_prompt,
'messages' => [
['role' => 'user', 'content' => $prompt]

Check warning on line 425 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

When a multi-item array uses associative keys, each value should start on a new line.
]
];

Expand Down Expand Up @@ -444,7 +451,7 @@
'anthropic-version' => '2023-06-01',
'Content-Type' => 'application/json'
],
'body' => json_encode($body),

Check warning on line 454 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

json_encode() is discouraged. Use wp_json_encode() instead.
'timeout' => 120
];

Expand Down Expand Up @@ -595,7 +602,7 @@
'headers' => [
'Content-Type' => 'application/json'
],
'body' => json_encode($body),

Check warning on line 605 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

json_encode() is discouraged. Use wp_json_encode() instead.
'timeout' => 120
];

Expand Down Expand Up @@ -717,7 +724,7 @@
}

// Load the prompt file
$system_prompt = file_get_contents( $prompt_file );

Check warning on line 727 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

file_get_contents() is discouraged. Use wp_remote_get() for remote URLs instead.

// Add form type context (informational, not restrictive)
$system_prompt .= "\n\n## FORM TYPE CONTEXT\n";
Expand Down Expand Up @@ -803,7 +810,7 @@
}
}

$system_prompt .= json_encode( $context_for_ai, JSON_PRETTY_PRINT );

Check warning on line 813 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

json_encode() is discouraged. Use wp_json_encode() instead.

// Add specific instruction for modifications
$system_prompt .= "\n\n## MODIFICATION INSTRUCTION\n";
Expand Down Expand Up @@ -854,37 +861,232 @@
}

/**
* Test connection to current provider
* Test connection to provider with optional overrides
*
* @since 4.2.1
*
* @param string $api_key Optional API key to test (if not provided, uses saved settings)
* @param string $provider Optional provider to test (if not provided, uses saved settings)
* @param string $model Optional model to test (if not provided, uses saved settings)
* @return array Test result
*/
public function test_connection() {
public function test_connection( $api_key = '', $provider = '', $model = '' ) {
try {
$test_prompt = 'Generate a simple contact form with name and email fields.';
$result = $this->generate_form($test_prompt, ['max_tokens' => 500]);
// Reload settings to get latest data
$this->load_settings();

// Store originals for restoration
$original_provider = $this->current_provider;
$original_model = $this->current_model;
$original_key = $this->api_key;

// Use provided values or fall back to saved settings
if ( ! empty( $provider ) ) {
$this->current_provider = $provider;
}

if ( ! empty( $model ) ) {
$this->current_model = $model;
}

if (isset($result['success']) && $result['success']) {
$test_api_key = ! empty( $api_key ) ? $api_key : $this->api_key;

// Validate API key exists
if ( empty( $test_api_key ) ) {
return [
'success' => true,
'success' => false,
'provider' => $this->current_provider,
'message' => 'Connection successful'
'message' => __( 'No API key provided', 'wp-user-frontend' ),
];
} else {
}

// Temporarily override the API key for testing
$this->api_key = $test_api_key;

// Make a simple API call based on provider
$result = $this->test_provider_api();

// Restore original values
$this->current_provider = $original_provider;
$this->current_model = $original_model;
$this->api_key = $original_key;

return $result;

} catch ( \Exception $e ) {
return [
'success' => false,
'provider' => $this->current_provider,
'message' => $e->getMessage(),
];
}
}

/**
* Test provider API with minimal request
*
* @since 4.2.1
*
* @return array Test result
*/
private function test_provider_api() {
switch ( $this->current_provider ) {
case 'openai':
return $this->test_openai_connection();

case 'anthropic':
return $this->test_anthropic_connection();

case 'google':
return $this->test_google_connection();

default:
return [
'success' => false,
'success' => false,
'provider' => $this->current_provider,
'message' => $result['message'] ?? 'Test failed'
'message' => __( 'Unknown provider', 'wp-user-frontend' ),
];
}
}
}

} catch (\Exception $e) {
/**
* Test OpenAI connection
*/
private function test_openai_connection() {
$response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', [
'timeout' => 30,
'headers' => [
'Authorization' => 'Bearer ' . $this->api_key,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( [
'model' => $this->current_model,
'messages' => [ [ 'role' => 'user', 'content' => 'Hi' ] ],

Check warning on line 964 in includes/AI/FormGenerator.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

When a multi-item array uses associative keys, each value should start on a new line.
'max_tokens' => 5,
] ),
] );

if ( is_wp_error( $response ) ) {
return [
'success' => false,
'provider' => $this->current_provider,
'message' => $e->getMessage()
'success' => false,
'provider' => 'openai',
'message' => $response->get_error_message(),
];
}

$status_code = wp_remote_retrieve_response_code( $response );

if ( $status_code === 200 ) {
return [
'success' => true,
'provider' => 'openai',
'message' => __( 'OpenAI connection successful!', 'wp-user-frontend' ),
];
}

$body = json_decode( wp_remote_retrieve_body( $response ), true );
$error_message = $body['error']['message'] ?? __( 'Connection failed', 'wp-user-frontend' );

return [
'success' => false,
'provider' => 'openai',
'message' => $error_message,
];
}

/**
* Test Anthropic connection
*/
private function test_anthropic_connection() {
$response = wp_remote_post( 'https://api.anthropic.com/v1/messages', [
'timeout' => 30,
'headers' => [
'x-api-key' => $this->api_key,
'anthropic-version' => '2023-06-01',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( [
'model' => $this->current_model,
'messages' => [ [ 'role' => 'user', 'content' => 'Hi' ] ],
'max_tokens' => 5,
] ),
] );

if ( is_wp_error( $response ) ) {
return [
'success' => false,
'provider' => 'anthropic',
'message' => $response->get_error_message(),
];
}

$status_code = wp_remote_retrieve_response_code( $response );

if ( $status_code === 200 ) {
return [
'success' => true,
'provider' => 'anthropic',
'message' => __( 'Anthropic connection successful!', 'wp-user-frontend' ),
];
}

$body = json_decode( wp_remote_retrieve_body( $response ), true );
$error_message = $body['error']['message'] ?? __( 'Connection failed', 'wp-user-frontend' );

return [
'success' => false,
'provider' => 'anthropic',
'message' => $error_message,
];
}

/**
* Test Google connection
*/
private function test_google_connection() {
$endpoint = str_replace( '{model}', $this->current_model, 'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent' );

$response = wp_remote_post( $endpoint, [
'timeout' => 30,
'headers' => [
'Content-Type' => 'application/json',
'x-goog-api-key' => $this->api_key,
],
'body' => wp_json_encode( [
'contents' => [
[ 'parts' => [ [ 'text' => 'Hi' ] ] ],
],
'generationConfig' => [
'maxOutputTokens' => 5,
],
] ),
] );

if ( is_wp_error( $response ) ) {
return [
'success' => false,
'provider' => 'google',
'message' => $response->get_error_message(),
];
}

$status_code = wp_remote_retrieve_response_code( $response );

if ( $status_code === 200 ) {
return [
'success' => true,
'provider' => 'google',
'message' => __( 'Google AI connection successful!', 'wp-user-frontend' ),
];
}

$body = json_decode( wp_remote_retrieve_body( $response ), true );
$error_message = $body['error']['message'] ?? __( 'Connection failed', 'wp-user-frontend' );

return [
'success' => false,
'provider' => 'google',
'message' => $error_message,
];
}
}
29 changes: 26 additions & 3 deletions includes/AI/RestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,26 @@ public function register_routes() {

// Test connection endpoint
register_rest_route($this->namespace, '/' . $this->rest_base . '/test', [
'methods' => WP_REST_Server::READABLE,
'methods' => WP_REST_Server::CREATABLE,
'callback' => [$this, 'test_connection'],
'permission_callback' => [$this, 'check_permission']
'permission_callback' => [$this, 'check_permission'],
'args' => [
'api_key' => [
'required' => false,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field'
],
'provider' => [
'required' => false,
'type' => 'string',
'enum' => ['openai', 'anthropic', 'google']
],
'model' => [
'required' => false,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field'
]
]
]);

// Get providers endpoint
Expand Down Expand Up @@ -387,7 +404,13 @@ private function validate_input($data, $rules) {
*/
public function test_connection(WP_REST_Request $request) {
try {
$result = $this->form_generator->test_connection();
// Get parameters from request
$api_key = $request->get_param('api_key');
$provider = $request->get_param('provider');
$model = $request->get_param('model');

// Pass provider and model to test_connection
$result = $this->form_generator->test_connection($api_key, $provider, $model);

return new WP_REST_Response($result, $result['success'] ? 200 : 400);

Expand Down
Loading
Loading