Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
227 changes: 214 additions & 13 deletions includes/AI/FormGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,26 @@ public function generate_form($prompt, $options = []) {
$original_model = $this->current_model;
$original_api_key = $this->api_key;

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

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 @@ -854,31 +861,58 @@ public function get_current_provider() {
}

/**
* 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();

if (isset($result['success']) && $result['success']) {
return [
'success' => true,
'provider' => $this->current_provider,
'message' => 'Connection successful'
];
} else {
// 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;
}

$test_api_key = !empty($api_key) ? $api_key : $this->api_key;

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

// 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,
Expand All @@ -887,4 +921,171 @@ public function test_connection() {
];
}
}

/**
* 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,
'provider' => $this->current_provider,
'message' => __('Unknown provider', 'wp-user-frontend')
];
}
}

/**
* 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']],
'max_tokens' => 5
])
]);

if (is_wp_error($response)) {
return [
'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 . '?key=' . $this->api_key, [
'timeout' => 30,
'headers' => [
'Content-Type' => 'application/json'
],
'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