File "wpaicg_playground.php"
Full Path: /home/digimqhe/flashdigi.uk/comment-content/plugins/gpt3-ai-content-generator/classes/wpaicg_playground.php
File size: 63.17 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
namespace WPAICG;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists('\\WPAICG\\WPAICG_Playground')) {
class WPAICG_Playground {
/**
* The singleton instance.
*
* @var null|WPAICG_Playground
*/
private static $instance = null;
/**
* The configured AI provider, e.g. "OpenAI", "Google", "Azure", etc.
*
* @var string
*/
private string $provider;
/**
* Retrieve the singleton instance of this class.
*
* @return WPAICG_Playground
*/
public static function get_instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor hooks needed actions.
*/
public function __construct() {
/**
* We store the default provider once here.
* It may be overridden later in wpaicg_stream() if
* the request originates from an AI form (source_stream === 'form').
*/
$this->provider = get_option('wpaicg_provider', 'OpenAI');
add_action('init', [$this, 'wpaicg_stream'], 1);
add_action('wp_ajax_wpaicg_generate_content_google', [$this, 'wpaicg_generate_content_google']);
add_action('wp_ajax_save_wpaicg_google_api_key', [$this, 'save_wpaicg_google_api_key']);
add_action('wp_ajax_save_wpaicg_togetherai_api_key', [$this, 'save_wpaicg_togetherai_api_key']);
add_action('wp_ajax_wpaicg_generate_content_togetherai', [$this, 'wpaicg_generate_content_togetherai']);
}
/**
* Handles saving the Together AI API key.
*/
public function save_wpaicg_togetherai_api_key() {
check_ajax_referer('wpaicg-save-togetherai-api', 'nonce');
// Check if the current user has the capability to manage options
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
return;
}
$apiKey = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
update_option('wpaicg_togetherai_model_api_key', $apiKey);
wp_send_json_success(['message' => 'API key saved successfully']);
}
/**
* Handles saving the Google API key.
*/
public function save_wpaicg_google_api_key() {
check_ajax_referer('wpaicg-save-google-api', 'nonce');
// Check if the current user has the capability to manage options
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
return;
}
$apiKey = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
update_option('wpaicg_google_model_api_key', $apiKey);
wp_send_json_success(['message' => 'API key saved successfully']);
}
/**
* AJAX callback for generating content via Google.
*/
public function wpaicg_generate_content_google() {
check_ajax_referer('wpaicg_generate_content_google', 'nonce');
$title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : '';
$model = isset($_POST['model']) ? sanitize_text_field($_POST['model']) : '';
$response = $this->send_google_request($title, $model);
wp_send_json_success(['content' => $response]);
}
/**
* Actually sends the request to the Google API endpoint.
*
* @param string $title
* @param string $model
* @return string
*/
private function send_google_request($title, $model) {
$userPrompt = $title;
$apiKey = get_option('wpaicg_google_model_api_key', '');
if (empty($apiKey)) {
return 'Error: Google API key is not set';
}
// Dynamically construct the URL using the model name
$url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$apiKey}";
$args = array(
'headers' => array('Content-Type' => 'application/json'),
'method' => 'POST',
'timeout' => 300,
'body' => json_encode([
"contents" => [
["role" => "user", "parts" => [["text" => $userPrompt]]]
],
"generationConfig" => [
"temperature" => 0.9,
"topK" => 1,
"topP" => 1,
"maxOutputTokens" => 2048,
"stopSequences" => []
],
"safetySettings" => [
["category" => "HARM_CATEGORY_HARASSMENT", "threshold" => "BLOCK_MEDIUM_AND_ABOVE"],
]
])
);
$response = wp_safe_remote_post($url, $args);
if (is_wp_error($response)) {
return 'HTTP request error: ' . $response->get_error_message();
}
$body = wp_remote_retrieve_body($response);
$decodedResponse = json_decode($body, true);
if (isset($decodedResponse['error'])) {
$errorMsg = $decodedResponse['error']['message'] ?? 'Unknown error from Google API';
return 'Error: ' . $errorMsg;
}
// Check the expected response structure based on the API documentation
if (isset($decodedResponse['candidates'][0]['content']['parts'][0]['text'])) {
return $decodedResponse['candidates'][0]['content']['parts'][0]['text'];
} else {
return 'Error: Invalid response from Google API';
}
}
/**
* AJAX callback for generating content via Together AI.
*/
public function wpaicg_generate_content_togetherai() {
check_ajax_referer('wpaicg_generate_content_togetherai', 'nonce');
$title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : '';
$model = isset($_POST['model']) ? sanitize_text_field($_POST['model']) : '';
$response = $this->send_togetherai_request($title, $model);
wp_send_json_success(['content' => $response]);
}
/**
* Sends the request to the Together AI endpoint.
*
* @param string $title
* @param string $model
* @return string
*/
private function send_togetherai_request($title, $model) {
$apiKey = get_option('wpaicg_togetherai_model_api_key', '');
if (empty($apiKey)) {
return 'Error: Together AI API key is not set';
}
$url = "https://api.together.xyz/inference";
$args = [
'method' => 'POST',
'timeout' => 300,
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $apiKey
],
'body' => json_encode([
"model" => $model,
"max_tokens" => 2000,
"prompt" => $title,
"request_type" => "language-model-inference",
"temperature" => 0.7,
"top_p" => 0.7,
"top_k" => 50,
"repetition_penalty" => 1,
"stream_tokens" => true,
"stop" => [ "</s>", "[INST]" ],
"negative_prompt" => "",
"sessionKey" => "your_session_key",
"repetitive_penalty" => 1,
"update_at" => current_time('c')
])
];
$response = wp_safe_remote_post($url, $args);
if (is_wp_error($response)) {
return 'HTTP request error: ' . $response->get_error_message();
}
$body = wp_remote_retrieve_body($response);
return $this->process_stream_response($body);
}
/**
* Parses the streaming JSON data from Together AI.
*
* @param string $body
* @return string
*/
private function process_stream_response($body) {
$lines = explode("\n", $body);
$fullText = '';
foreach ($lines as $line) {
if (strpos($line, 'data: ') === 0) {
$jsonString = substr($line, 6); // Remove 'data: ' prefix
$data = json_decode($jsonString, true);
if (isset($data['choices'][0]['text'])) {
$fullText .= $data['choices'][0]['text'];
}
}
}
return $fullText;
}
/**
* Handles token limits for different sources.
*
* @param string $source
* @return array
*/
public function wpaicg_token_handling($source) {
global $wpdb;
$result = [];
$result['message'] = esc_html__('You have reached your token limit.','gpt3-ai-content-generator');
$result['table'] = 'wpaicg_formtokens';
$result['limit'] = false;
$result['tokens'] = 0;
$result['source'] = $source;
$result['token_id'] = false;
$result['limited'] = false;
$result['old_tokens'] = 0;
if (!is_user_logged_in()) {
$wpaicg_client_id = $this->wpaicg_get_cookie_id($source);
} else {
$wpaicg_client_id = false;
}
$result['client_id'] = $wpaicg_client_id;
if ($result['source'] === 'promptbase') {
$result['table'] = 'wpaicg_prompttokens';
}
if ($result['source'] === 'image') {
$result['table'] = 'wpaicg_imagetokens';
}
$wpaicg_settings = get_option('wpaicg_limit_tokens_' . $result['source'], []);
$result['message'] = isset($wpaicg_settings['limited_message']) && !empty($wpaicg_settings['limited_message'])
? wp_unslash($wpaicg_settings['limited_message'])
: $result['message'];
// Check user-based limits
if (
is_user_logged_in()
&& isset($wpaicg_settings['user_limited'])
&& $wpaicg_settings['user_limited']
&& $wpaicg_settings['user_tokens'] > 0
) {
$result['limit'] = true;
$result['tokens'] = $wpaicg_settings['user_tokens'];
}
// Check role-based limits
if (
is_user_logged_in()
&& isset($wpaicg_settings['role_limited'])
&& $wpaicg_settings['role_limited']
) {
$wpaicg_roles = (array) wp_get_current_user()->roles;
$limited_current_role = 0;
foreach ($wpaicg_roles as $wpaicg_role) {
if (
isset($wpaicg_settings['limited_roles'])
&& is_array($wpaicg_settings['limited_roles'])
&& isset($wpaicg_settings['limited_roles'][$wpaicg_role])
&& $wpaicg_settings['limited_roles'][$wpaicg_role] > $limited_current_role
) {
$limited_current_role = $wpaicg_settings['limited_roles'][$wpaicg_role];
}
}
if ($limited_current_role > 0) {
$result['limit'] = true;
$result['tokens'] = $limited_current_role;
} else {
$result['limit'] = false;
}
}
// Guest-based limits
if (
!is_user_logged_in()
&& isset($wpaicg_settings['guest_limited'])
&& $wpaicg_settings['guest_limited']
&& $wpaicg_settings['guest_tokens'] > 0
) {
$result['limit'] = true;
$result['tokens'] = $wpaicg_settings['guest_tokens'];
}
// If there's an overall limit set, check current token usage
if ($result['limit']) {
if (is_user_logged_in()) {
$wpaicg_chat_token_log = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}{$result['table']} WHERE user_id=%d",
get_current_user_id()
)
);
} else {
$wpaicg_chat_token_log = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}{$result['table']} WHERE session_id=%s",
$wpaicg_client_id
)
);
}
$result['old_tokens'] = $wpaicg_chat_token_log ? $wpaicg_chat_token_log->tokens : 0;
$wpaicg_chat_token_id = $wpaicg_chat_token_log ? $wpaicg_chat_token_log->id : false;
if (
$result['old_tokens'] > 0
&& $result['tokens'] > 0
&& $result['old_tokens'] > $result['tokens']
) {
$result['limited'] = true;
$result['token_id'] = $wpaicg_chat_token_id;
$result['left_tokens'] = 0;
} else {
$result['left_tokens'] = $result['tokens'] - $result['old_tokens'];
$result['token_id'] = $wpaicg_chat_token_id;
$result['limited'] = false;
}
// Check if logged user has limit tokens in user_meta
if (is_user_logged_in()) {
$user_meta_key = 'wpaicg_' . ($result['source'] === 'form' ? 'forms' : $result['source']) . '_tokens';
$user_tokens = get_user_meta(get_current_user_id(), $user_meta_key, true);
$result['left_tokens'] += (float) $user_tokens;
}
// If system says "limited," but user has user-meta tokens left
if ($result['limited'] && is_user_logged_in()) {
if (!empty($user_tokens) && $user_tokens > 0) {
$result['limited'] = false;
}
}
}
return $result;
}
/**
* Retrieve the final prompt text by merging user inputs into placeholders.
*
* @param int $post_id
* @return string
*/
public function get_defined_prompt($post_id) {
$form_fields = get_post_meta($post_id, 'wpaicg_form_fields', true);
$defined_prompt = get_post_meta($post_id, 'wpaicg_form_prompt', true);
if (empty($form_fields) || empty($defined_prompt)) {
if (file_exists(WPAICG_PLUGIN_DIR . 'admin/data/gptforms.json')) {
$forms_data = json_decode(file_get_contents(WPAICG_PLUGIN_DIR . 'admin/data/gptforms.json'), true);
foreach ($forms_data as $form) {
if (isset($form['id']) && $form['id'] == $post_id) {
$form_fields = json_encode($form['fields']);
$defined_prompt = $form['prompt'];
break;
}
}
}
}
// Function to fix common JSON issues
function fix_json($json) {
// Remove BOM
$json = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $json);
// Fix escaped quotes
$json = str_replace("\\'", "'", $json);
// Ensure double quotes for property names
$json = preg_replace('/(\w+)(?=\s*:)/','"$1"',$json);
return $json;
}
$decoded_fields = json_decode($form_fields, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$fixed_json = fix_json($form_fields);
$decoded_fields = json_decode($fixed_json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return "Error: Unable to process form fields.";
}
}
if (!is_array($decoded_fields)) {
return "Error: Invalid form structure.";
}
$field_values = [];
// Handle user-submitted data for each field
foreach ($decoded_fields as $field) {
if (isset($field['id'], $_REQUEST[$field['id']])) {
if (is_array($_REQUEST[$field['id']])) {
// e.g. multiple checkboxes
$values = array_map('sanitize_text_field', $_REQUEST[$field['id']]);
$field_values[$field['id']] = implode(', ', $values);
} else {
// Single value (text, fileupload, etc.)
$rawValue = sanitize_text_field($_REQUEST[$field['id']]);
// If it references our transient key, retrieve the actual content
if (strpos($rawValue, 'wpaicg_upload_') === 0) {
$fileData = get_transient($rawValue);
if ($fileData !== false) {
$field_values[$field['id']] = $fileData;
} else {
// If the transient is missing/expired, store as empty
$field_values[$field['id']] = '';
}
} else {
$field_values[$field['id']] = $rawValue;
}
}
}
}
// Replace placeholders {field_id} with user-submitted values
foreach ($field_values as $key => $value) {
$defined_prompt = str_replace('{' . $key . '}', $value, $defined_prompt);
}
return $defined_prompt;
}
/**
* Action to handle streaming responses from AI providers.
* Primarily used for SSE (Server-Sent Events) output.
*/
public function wpaicg_stream()
{
if (isset($_GET['wpaicg_stream']) && sanitize_text_field($_GET['wpaicg_stream']) === 'yes') {
global $wpdb;
header('Content-type: text/event-stream');
header('Cache-Control: no-cache');
if (! wp_verify_nonce($_REQUEST['nonce'], 'wpaicg-ajax-nonce')) {
$wpaicg_error_message = esc_html__('Nonce verification failed', 'gpt3-ai-content-generator');
$this->wpaicg_event_message($wpaicg_error_message);
exit;
}
$source = isset($_REQUEST['source_stream']) ? sanitize_text_field($_REQUEST['source_stream']) : '';
$wpaicg_prompt = '';
$post_id = 0;
// Playground & Promptbase
if ($source === 'playground' || $source === 'promptbase') {
if (isset($_REQUEST['title']) && !empty($_REQUEST['title'])) {
$wpaicg_prompt = sanitize_text_field($_REQUEST['title']);
}
} else {
// AI Forms
if (isset($_REQUEST['id']) && !empty($_REQUEST['id'])) {
$post_id = intval($_REQUEST['id']);
$wpaicg_prompt = $this->get_defined_prompt($post_id);
}
}
if (!$wpaicg_prompt) {
exit;
}
// ------------------------------------
// 1) Check if user enabled Internet Browsing for this form/prompt
// If yes, fetch Google search results and append to prompt
// ------------------------------------
if ($source === 'form' && !empty($post_id)) {
$internet_browsing = get_post_meta($post_id, 'wpaicg_form_internet_browsing', true);
if ($internet_browsing === 'yes') {
$googleSearch = $this->handle_internet_search($wpaicg_prompt);
if (!empty($googleSearch)) {
// Append the search results
$wpaicg_prompt .= "\n" . $googleSearch;
}
}
} elseif ($source === 'promptbase' && !empty($post_id)) {
// If you also support “internet” for Promptbase,
// you could do something similar here, e.g.:
// $internet_browsing = get_post_meta($post_id, 'wpaicg_prompt_internet_browsing', true);
// if ($internet_browsing === 'yes') { ... }
}
// ------------------------------------
// 2) Check if embeddings are enabled
// ------------------------------------
$embeddingsDetails = $this->get_embeddings_details();
if ($embeddingsDetails['embeddingsEnabled']) {
$wpaicg_prompt = $this->handle_embeddings($wpaicg_prompt, $embeddingsDetails);
}
// ------------------------------------
// 3) Initialize the correct AI provider
// (possibly overridden if source=‘form’ or ‘promptbase’)
// ------------------------------------
try {
// If the request is from an AI form, we override $this->provider
if ($source === 'form' && !empty($post_id)) {
$providerFromMeta = get_post_meta($post_id, 'wpaicg_form_model_provider', true);
if (!empty($providerFromMeta)) {
$this->provider = $providerFromMeta;
}
} elseif ($source === 'promptbase' && !empty($post_id)) {
$providerFromMeta = get_post_meta($post_id, 'wpaicg_prompt_model_provider', true);
if (!empty($providerFromMeta)) {
$this->provider = $providerFromMeta;
}
}
$ai_engine = \WPAICG\WPAICG_Util::get_instance()->initialize_ai_engine($this->provider);
} catch (\Exception $e) {
$wpaicg_result['msg'] = $e->getMessage();
wp_send_json($wpaicg_result);
}
if (!$ai_engine) {
exit;
}
// ------------------------------------
// 4) Check if user is token-limited
// ------------------------------------
$has_limited = false;
if (in_array($source, ['promptbase','form'], true)) {
$wpaicg_token_handling = $this->wpaicg_token_handling($source);
if ($wpaicg_token_handling['limited']) {
$has_limited = true;
$this->wpaicg_event_message($wpaicg_token_handling['message']);
}
}
// ------------------------------------
// 5) If not limited, proceed with SSE
// ------------------------------------
if (!$has_limited) {
// If the provider is Google, handle it differently
if ($this->provider === 'Google') {
$google_model = (isset($_REQUEST['engine']) && !empty($_REQUEST['engine']))
? sanitize_text_field($_REQUEST['engine'])
: get_option('wpaicg_google_default_model', 'gemini-pro');
$wpaicg_args = [
'messages' => [
['content' => $wpaicg_prompt]
],
'model' => $google_model,
'temperature' => 0.9,
'top_p' => 1,
'max_tokens' => 2048,
'sourceModule'=> 'form'
];
$response = $ai_engine->chat($wpaicg_args);
$words = [];
if (isset($response['error']) && !empty($response['error'])) {
$words = explode(' ', $response['error']);
} else {
$words = explode(' ', $response['data']);
}
foreach ($words as $key => $word) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo "event: message\n";
if ($key === 0) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo 'data: {"choices":[{"delta":{"content":"' . $word . '"}}]}';
} else {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo 'data: {"choices":[{"delta":{"content":" ' . $word . '"}}]}';
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo "\n\n";
if (ob_get_level() > 0) {
ob_end_flush();
}
flush();
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo 'data: [DONE]' . "\n\n";
if (ob_get_length()) {
ob_flush();
flush();
}
} else {
// For OpenAI / Azure / OpenRouter
$wpaicg_args = $this->build_ai_args($ai_engine, $wpaicg_prompt);
$legacy_models = [
'text-davinci-001','davinci','babbage','text-babbage-001','curie-instruct-beta','text-davinci-003',
'text-curie-001','davinci-instruct-beta','text-davinci-002','ada','text-ada-001','curie','gpt-3.5-turbo-instruct'
];
if (!in_array($wpaicg_args['model'], $legacy_models, true)) {
// Chat endpoint
unset($wpaicg_args['best_of']);
$wpaicg_args['messages'] = [
['role' => 'user', 'content' => $wpaicg_args['prompt']]
];
unset($wpaicg_args['prompt']);
try {
$ai_engine->chat($wpaicg_args, function ($curl_info, $data) {
$this->handle_sse_callback($data);
return strlen($data);
});
} catch (\Exception $exception) {
$message = $exception->getMessage();
$this->wpaicg_event_message($message);
}
} else {
// Legacy completion endpoint
try {
$ai_engine->completion($wpaicg_args, function ($curl_info, $data) {
$this->handle_sse_callback($data);
return strlen($data);
});
} catch (\Exception $exception) {
$message = $exception->getMessage();
$this->wpaicg_event_message($message);
}
}
}
}
exit;
}
}
/**
* If "internet browsing" is enabled for this AI Form,
* perform a Google search with the user’s prompt and return results.
* This is appended to the main $wpaicg_prompt before sending to the AI.
*/
private function handle_internet_search(string $prompt): string
{
// Retrieve necessary settings
$google_api_key = get_option('wpaicg_google_api_key', '');
$google_search_engine_id = get_option('wpaicg_google_search_engine_id', '');
if (empty($google_api_key) || empty($google_search_engine_id)) {
// If no API key or engine ID, simply return nothing
return '';
}
// Sanitize the search query
$search_query = sanitize_text_field($prompt);
// Execute the search
$results = $this->wpaicg_search_internet($google_api_key, $google_search_engine_id, $search_query);
if ($results['status'] === 'success' && !empty($results['data'])) {
// Return raw string of results, optionally label them
return "\n" . esc_html__('Internet Search Results:', 'gpt3-ai-content-generator')
. "\n" . $results['data'];
}
return '';
}
/**
* Perform a Google CSE (Custom Search Engine) call using the provided API key, engine ID, and query.
* Returns an array with ['status'=>'success','data'=>'...'] or ['status'=>'error','data'=>''].
*/
public function wpaicg_search_internet(string $api_key, string $search_engine_id, string $query): array
{
$country = get_option('wpaicg_google_search_country', '');
$num_results= get_option('wpaicg_google_search_num', 10);
$language = get_option('wpaicg_google_search_language', '');
$search_url = 'https://www.googleapis.com/customsearch/v1?q=' . urlencode($query)
. '&key=' . $api_key
. '&cx=' . $search_engine_id;
if (!empty($country)) {
$search_url .= '&cr=' . urlencode($country);
}
if (!empty($language)) {
$search_url .= '&lr=' . urlencode($language);
}
$search_url .= '&num=' . intval($num_results);
$response = wp_remote_get($search_url);
if (is_wp_error($response)) {
return ['status' => 'error', 'data' => ''];
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (isset($data['items']) && !empty($data['items'])) {
$search_content = '';
// Build a short textual snippet from each result
foreach ($data['items'] as $item) {
$title = isset($item['title']) ? $item['title'] : '';
$snippet = isset($item['snippet']) ? $item['snippet'] : '';
$link = isset($item['link']) ? $item['link'] : '';
$search_content .= $title . "\n" . $snippet . "\n" . $link . "\n\n";
}
return [
'status' => 'success',
'data' => $search_content
];
}
return ['status' => 'empty', 'data' => ''];
}
/**
* Helper to fetch embeddings details if provided in form/prompt meta.
*
* @return array
*/
public function get_embeddings_details() {
// Must check presence of required params
if (
isset($_REQUEST['id'], $_REQUEST['source_stream'])
&& !empty($_REQUEST['id'])
&& in_array($_REQUEST['source_stream'], ['promptbase','form'], true)
) {
$wpaicg_post_id = intval($_REQUEST['id']);
$source = $_REQUEST['source_stream'];
if ($source === 'form') {
$embeddingsEnabled = (get_post_meta($wpaicg_post_id, 'wpaicg_form_embeddings', true) === 'yes');
$use_default_embedding_model = get_post_meta($wpaicg_post_id, 'wpaicg_form_use_default_embedding_model', true);
$selected_embedding_model = get_post_meta($wpaicg_post_id, 'wpaicg_form_selected_embedding_model', true);
$selected_embedding_provider = get_post_meta($wpaicg_post_id, 'wpaicg_form_selected_embedding_provider', true);
$context_suffix = get_post_meta($wpaicg_post_id, 'wpaicg_form_suffix_text', true);
$context_suffix_position = get_post_meta($wpaicg_post_id, 'wpaicg_form_suffix_position', true);
$embeddings_limit = get_post_meta($wpaicg_post_id, 'wpaicg_form_embeddings_limit', true);
$vectordb = get_post_meta($wpaicg_post_id, 'wpaicg_form_vectordb', true);
$collectionsOrIndexes = '';
if ($vectordb === 'qdrant') {
$collectionsOrIndexes = get_post_meta($wpaicg_post_id, 'wpaicg_form_collections', true);
} elseif ($vectordb === 'pinecone') {
$collectionsOrIndexes = get_post_meta($wpaicg_post_id, 'wpaicg_form_pineconeindexes', true);
}
} else {
$embeddingsEnabled = (get_post_meta($wpaicg_post_id, 'wpaicg_prompt_embeddings', true) === 'yes');
$use_default_embedding_model = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_use_default_embedding_model', true);
$selected_embedding_model = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_selected_embedding_model', true);
$selected_embedding_provider = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_selected_embedding_provider', true);
$context_suffix = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_suffix_text', true);
$context_suffix_position = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_suffix_position', true);
$embeddings_limit = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_embeddings_limit', true);
$vectordb = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_vectordb', true);
$collectionsOrIndexes = '';
if ($vectordb === 'qdrant') {
$collectionsOrIndexes = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_collections', true);
} elseif ($vectordb === 'pinecone') {
$collectionsOrIndexes = get_post_meta($wpaicg_post_id, 'wpaicg_prompt_pineconeindexes', true);
}
}
if(empty($embeddings_limit)){
$embeddings_limit = 1;
}
// Disable embeddings if provider is OpenRouter
if ($this->provider === 'OpenRouter') {
$embeddingsEnabled = false;
}
// If embeddings are enabled, return vectordb and collections or indexes meta values
if ($embeddingsEnabled) {
return [
'embeddingsEnabled' => true,
'vectordb' => $vectordb,
'collections' => $collectionsOrIndexes,
'context_suffix' => $context_suffix,
'context_suffix_position' => $context_suffix_position,
'embeddings_limit' => (int) $embeddings_limit,
'use_default_embedding_model' => $use_default_embedding_model,
'selected_embedding_model' => $selected_embedding_model,
'selected_embedding_provider' => $selected_embedding_provider
];
}
}
// By default, embeddings disabled
return ['embeddingsEnabled' => false];
}
/**
* Helper to handle Qdrant/Pinecone embeddings retrieval and merge with prompt.
*
* @param string $wpaicg_prompt
* @param array $embeddingsDetails
* @return string
*/
private function handle_embeddings($wpaicg_prompt, array $embeddingsDetails) {
$contextLabel = !empty($embeddingsDetails['context_suffix']) ? $embeddingsDetails['context_suffix'] : "";
$contextData = "";
if ($embeddingsDetails['vectordb'] === 'qdrant') {
$embedding_result = $this->wpaicg_embeddings_result_qdrant(
$embeddingsDetails['collections'],
$wpaicg_prompt,
$embeddingsDetails['embeddings_limit'],
$embeddingsDetails['use_default_embedding_model'],
$embeddingsDetails['selected_embedding_model'],
$embeddingsDetails['selected_embedding_provider']
);
if (!empty($embedding_result['data'])) {
$contextData = $contextLabel . " " . $embedding_result['data'];
}
} elseif ($embeddingsDetails['vectordb'] === 'pinecone') {
$embedding_result = $this->wpaicg_embeddings_result_pinecone(
$embeddingsDetails['collections'],
$wpaicg_prompt,
$embeddingsDetails['embeddings_limit'],
$embeddingsDetails['use_default_embedding_model'],
$embeddingsDetails['selected_embedding_model'],
$embeddingsDetails['selected_embedding_provider']
);
if (!empty($embedding_result['data'])) {
$contextData = $contextLabel . " " . $embedding_result['data'];
}
} else {
// If vectordb is neither 'qdrant' nor 'pinecone'
error_log("Embeddings enabled but no valid vector DB found.");
}
// Prepend or append context
if ($embeddingsDetails['context_suffix_position'] === 'before') {
return $contextData . " " . $wpaicg_prompt;
}
return $wpaicg_prompt . " " . $contextData;
}
/**
* Handle building AI arguments from request parameters.
*
* @param object $ai_engine
* @param string $prompt
* @return array
*/
private function build_ai_args($ai_engine, $prompt) {
$args = [
'prompt' => $prompt,
'temperature' => (float)$ai_engine->temperature,
'max_tokens' => (float)$ai_engine->max_tokens,
'frequency_penalty' => (float)$ai_engine->frequency_penalty,
'presence_penalty' => (float)$ai_engine->presence_penalty,
'stream' => true
];
if (isset($_REQUEST['temperature']) && !empty($_REQUEST['temperature'])) {
$args['temperature'] = (float)sanitize_text_field($_REQUEST['temperature']);
}
if (isset($_REQUEST['max_tokens']) && !empty($_REQUEST['max_tokens'])) {
$args['max_tokens'] = (float)sanitize_text_field($_REQUEST['max_tokens']);
}
if (isset($_REQUEST['frequency_penalty']) && !empty($_REQUEST['frequency_penalty'])) {
$args['frequency_penalty'] = (float)sanitize_text_field($_REQUEST['frequency_penalty']);
}
if (isset($_REQUEST['presence_penalty']) && !empty($_REQUEST['presence_penalty'])) {
$args['presence_penalty'] = (float)sanitize_text_field($_REQUEST['presence_penalty']);
}
if (isset($_REQUEST['top_p']) && !empty($_REQUEST['top_p'])) {
$args['top_p'] = (float)sanitize_text_field($_REQUEST['top_p']);
}
if (isset($_REQUEST['best_of']) && !empty($_REQUEST['best_of'])) {
$args['best_of'] = (float)sanitize_text_field($_REQUEST['best_of']);
}
if (isset($_REQUEST['stop']) && !empty($_REQUEST['stop'])) {
$args['stop'] = explode(',', sanitize_text_field($_REQUEST['stop']));
}
// Configure the model based on the provider
if ($this->provider === 'OpenAI') {
if (isset($_REQUEST['engine']) && !empty($_REQUEST['engine'])) {
$args['model'] = sanitize_text_field($_REQUEST['engine']);
} else {
// default model for OpenAI
$args['model'] = 'gpt-3.5-turbo-16k';
}
} elseif ($this->provider === 'Google') {
if (isset($_REQUEST['engine']) && !empty($_REQUEST['engine'])) {
$args['model'] = sanitize_text_field($_REQUEST['engine']);
} else {
$args['model'] = get_option('wpaicg_google_default_model', 'gemini-pro');
}
} elseif ($this->provider === 'OpenRouter') {
if (isset($_REQUEST['engine']) && !empty($_REQUEST['engine'])) {
$args['model'] = sanitize_text_field($_REQUEST['engine']);
} else {
$args['model'] = get_option('wpaicg_openrouter_default_model', 'openrouter/auto');
}
} else {
// Assume Azure for any other cases
$args['model'] = get_option('wpaicg_azure_deployment', '');
}
return $args;
}
/**
* Helper to handle SSE-like output for Google.
* Google responses are returned as a single chunk, so we artificially
* split them into words for SSE streaming.
*
* @param object $ai_engine
* @param array $wpaicg_args
* @return void
*/
private function handle_google_stream_sse($ai_engine, array $wpaicg_args) {
// This function is no longer used for Google since we now call the old code path directly in wpaicg_stream()
}
/**
* Helper to handle streaming for providers like OpenAI / Azure that
* support chunked responses.
*
* @param object $ai_engine
* @param array $wpaicg_args
* @return void
*/
private function handle_openai_like_stream($ai_engine, array $wpaicg_args) {
$legacy_models = [
'text-davinci-001','davinci','babbage','text-babbage-001','curie-instruct-beta','text-davinci-003',
'text-curie-001','davinci-instruct-beta','text-davinci-002','ada','text-ada-001','curie','gpt-3.5-turbo-instruct'
];
// If it's not a legacy model, treat as Chat-based
if (! in_array($wpaicg_args['model'], $legacy_models, true)) {
unset($wpaicg_args['best_of']);
$wpaicg_args['messages'] = [
['role' => 'user', 'content' => $wpaicg_args['prompt']]
];
unset($wpaicg_args['prompt']);
// Attempt streaming chat
try {
$ai_engine->chat($wpaicg_args, function ($curl_info, $data) {
$this->handle_sse_callback($data);
return strlen($data);
});
} catch (\Exception $exception) {
$message = $exception->getMessage();
$this->wpaicg_event_message($message);
}
} else {
// Legacy-based completion call
try {
$ai_engine->completion($wpaicg_args, function ($curl_info, $data) {
$this->handle_sse_callback($data);
return strlen($data);
});
} catch (\Exception $exception) {
$message = $exception->getMessage();
$this->wpaicg_event_message($message);
}
}
}
/**
* SSE callback for partial data; handles both normal and error outputs.
*
* @param string $data
* @return void
*/
private function handle_sse_callback($data) {
$response = json_decode($data, true);
// If there's an error, we stream it out as words, then finalize
if (isset($response['error']) && !empty($response['error'])) {
$message = $response['error']['message'] ?? '';
if (empty($message) && isset($response['error']['code']) && $response['error']['code'] === 'invalid_api_key') {
$message = "Incorrect API key provided. You can find your API key at https://platform.openai.com/account/api-keys.";
}
$this->stream_words_as_sse($message);
// Send final SSE chunk with finish_reason
echo 'data: {"choices":[{"finish_reason":"stop"}]}' . "\n\n";
ob_flush();
flush();
return;
}
// Otherwise just echo raw chunk
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo $data;
ob_flush();
flush();
}
/**
* Helper method to split a text message into space-delimited words
* and stream them as SSE.
*
* @param string $message
* @return void
*/
private function stream_words_as_sse($message) {
$words = explode(' ', $message);
foreach ($words as $key => $word) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo "event: message\n";
if ($key === 0) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo 'data: {"choices":[{"delta":{"content":"' . $word . '"}}]}';
} else {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo 'data: {"choices":[{"delta":{"content":" ' . $word . '"}}]}';
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo "\n\n";
if (ob_get_level() > 0) {
ob_end_flush();
}
flush();
}
}
/**
* Send a message in SSE format, splitting content by space
* and appending the "[LIMITED]" marker.
*
* @param string $words
* @return void
*/
public function wpaicg_event_message($words) {
$splitWords = explode(' ', $words);
// Append the special marker to indicate limitation
$splitWords[] = '[LIMITED]';
foreach ($splitWords as $key => $word) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo "event: message\n";
if ($key === 0) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo 'data: {"choices":[{"delta":{"content":"' . $word . '"}}]}';
} else {
if ($word === '[LIMITED]') {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo 'data: [LIMITED]';
} else {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo 'data: {"choices":[{"delta":{"content":" ' . $word . '"}}]}';
}
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputting raw SSE JSON data; client must handle content safely.
echo "\n\n";
if (ob_get_level() > 0) {
ob_end_flush();
}
flush();
}
}
/**
* Retrieve or set a unique cookie ID for non-logged-in users.
*
* @param string $source_stream
* @return string
*/
public function wpaicg_get_cookie_id($source_stream) {
if(!function_exists('PasswordHash')){
require_once ABSPATH . 'wp-includes/class-phpass.php';
}
$cookieName = 'wpaicg_' . $source_stream . '_client_id';
if (isset($_COOKIE[$cookieName]) && !empty($_COOKIE[$cookieName])) {
return $_COOKIE[$cookieName];
}
$hasher = new \PasswordHash(8, false);
$cookie_id = 't_' . substr(md5($hasher->get_random_bytes(32)), 2);
setcookie($cookieName, $cookie_id, time() + 604800, COOKIEPATH, COOKIE_DOMAIN);
return $cookie_id;
}
/**
* Handle embeddings with Pinecone.
*
* @param string $wpaicg_pinecone_environment
* @param string $wpaicg_message
* @param int $limit
* @param string $use_default_embedding_model
* @param string $selected_embedding_model
* @param string $selected_embedding_provider
* @param bool $namespace
* @return array
*/
public function wpaicg_embeddings_result_pinecone(
$wpaicg_pinecone_environment,
$wpaicg_message,
$limit,
$use_default_embedding_model,
$selected_embedding_model,
$selected_embedding_provider,
$namespace = false
) {
$result = ['status' => 'error', 'data' => ''];
$wpaicg_pinecone_api_key= get_option('wpaicg_pinecone_api', '');
if (empty($wpaicg_pinecone_api_key) || empty($wpaicg_pinecone_environment)) {
return ['data' => esc_html__('Required Pinecone or API configuration missing.', 'gpt3-ai-content-generator')];
}
$model = $this->get_embedding_model($use_default_embedding_model, $selected_embedding_model);
$apiParams = $this->prepare_api_params($wpaicg_message, $model);
$ai_instance = $this->initialize_ai_instance($use_default_embedding_model, $selected_embedding_provider);
if (!$ai_instance) {
return ['data' => esc_html__('Unable to initialize the AI instance.', 'gpt3-ai-content-generator')];
}
$response = $ai_instance->embeddings($apiParams);
$response = json_decode($response, true);
if (isset($response['error']) && !empty($response['error'])) {
$errorMessage = $response['error']['message'] ?? 'Incorrect API key provided.';
return ['data' => $errorMessage];
}
$embedding = $response['data'][0]['embedding'] ?? null;
if (empty($embedding)) {
return ['data' => esc_html__('No embedding data received from the AI provider.', 'gpt3-ai-content-generator')];
}
// Calculate prompt_tokens based on $wpaicg_message character count
$contentLength = strlen($wpaicg_message);
$promptTokens = ceil($contentLength / 4);
// Convert to OpenAI format
$openAiResponse = [
"object" => "list",
"data" => [
[
"object" => "embedding",
"index" => 0,
"embedding" => $embedding
]
],
"model" => $model,
"usage" => [
"prompt_tokens" => $promptTokens,
"total_tokens" => $promptTokens
]
];
$decodedResponse = json_encode($openAiResponse);
return $decodedResponse;
}
/**
* Perform the Pinecone query to search for nearest matches.
*
* @param string $wpaicg_pinecone_environment
* @param array $embedding
* @param string $wpaicg_pinecone_api_key
* @param int $limit
* @param bool $namespace
* @return array
*/
private function search_pinecone($wpaicg_pinecone_environment, $embedding, $wpaicg_pinecone_api_key, $limit, $namespace) {
$headers = [
'Content-Type' => 'application/json',
'Api-Key' => $wpaicg_pinecone_api_key
];
$pinecone_body = [
'vector' => $embedding,
'topK' => $limit
];
if ($namespace) {
$pinecone_body['namespace'] = $namespace;
}
$response = wp_safe_remote_post("https://{$wpaicg_pinecone_environment}/query", [
'headers' => $headers,
'body' => json_encode($pinecone_body)
]);
if (is_wp_error($response)) {
return ['data' => esc_html($response->get_error_message())];
}
$bodyContent = wp_remote_retrieve_body($response);
$body = json_decode($bodyContent, true);
if (
isset($body['matches'])
&& is_array($body['matches'])
&& count($body['matches'])
) {
$data = '';
$processedCount = 0;
foreach ($body['matches'] as $match) {
if ($processedCount >= $limit) {
break;
}
$wpaicg_embedding = get_post($match['id']);
if ($wpaicg_embedding) {
$data .= empty($data) ? $wpaicg_embedding->post_content : "\n" . $wpaicg_embedding->post_content;
}
$processedCount++;
}
return ['status' => 'success', 'data' => $data];
}
return [
'status' => 'error',
'data' => esc_html__('No matches found or error in Pinecone response.', 'gpt3-ai-content-generator')
];
}
/**
* Handle embeddings with Qdrant.
*
* @param string $wpaicg_qdrant_collection
* @param string $wpaicg_message
* @param int $limit
* @param string $use_default_embedding_model
* @param string $selected_embedding_model
* @param string $selected_embedding_provider
* @return array
*/
public function wpaicg_embeddings_result_qdrant(
$wpaicg_qdrant_collection,
$wpaicg_message,
$limit,
$use_default_embedding_model,
$selected_embedding_model,
$selected_embedding_provider
) {
$result = ['status' => 'error', 'data' => ''];
$wpaicg_qdrant_api_key = get_option('wpaicg_qdrant_api_key', '');
$wpaicg_qdrant_endpoint = get_option('wpaicg_qdrant_endpoint', '');
if (
empty($wpaicg_qdrant_api_key)
|| empty($wpaicg_qdrant_endpoint)
|| empty($wpaicg_qdrant_collection)
) {
return ['data' => esc_html__('Required Qdrant or API configuration missing.', 'gpt3-ai-content-generator')];
}
$model = $this->get_embedding_model($use_default_embedding_model, $selected_embedding_model);
$apiParams = $this->prepare_api_params($wpaicg_message, $model);
$ai_instance = $this->initialize_ai_instance($use_default_embedding_model, $selected_embedding_provider);
if (!$ai_instance) {
return ['data' => esc_html__('Unable to initialize the AI instance.', 'gpt3-ai-content-generator')];
}
$response = $ai_instance->embeddings($apiParams);
$response = json_decode($response, true);
if (isset($response['error']) && !empty($response['error'])) {
$errorMessage = $response['error']['message'] ?? 'Incorrect API key provided.';
return ['data' => $errorMessage];
}
$embedding = $response['data'][0]['embedding'] ?? null;
if (empty($embedding)) {
return ['data' => esc_html__('No embedding data received from the AI provider.', 'gpt3-ai-content-generator')];
}
$result = $this->search_qdrant($wpaicg_qdrant_endpoint, $wpaicg_qdrant_collection, $embedding, $limit, $wpaicg_qdrant_api_key);
return $result;
}
/**
* Get the embedding model name from either default or custom.
*
* @param string $use_default_embedding_model
* @param string $selected_embedding_model
* @return string
*/
private function get_embedding_model($use_default_embedding_model, $selected_embedding_model) {
if ($use_default_embedding_model === 'no') {
return $selected_embedding_model;
}
$main_embedding_model = get_option('wpaicg_main_embedding_model', '');
if (!empty($main_embedding_model)) {
list($provider, $model) = explode(':', $main_embedding_model, 2);
return $model;
} else {
$wpaicg_provider = get_option('wpaicg_provider', 'OpenAI');
// Retrieve the embedding model based on the provider
switch ($wpaicg_provider) {
case 'OpenAI':
return get_option('wpaicg_openai_embeddings', 'text-embedding-ada-002');
case 'Azure':
return get_option('wpaicg_azure_embeddings', 'text-embedding-ada-002');
case 'Google':
return get_option('wpaicg_google_embeddings', 'embedding-001');
default:
return 'default-embedding-model';
}
}
}
/**
* Prepare the API params for embeddings calls.
*
* @param string $wpaicg_message
* @param string $model
* @return array
*/
private function prepare_api_params($wpaicg_message, $model) {
return [
'input' => $wpaicg_message,
'model' => $model
];
}
/**
* Initialize the correct AI instance for embeddings calls.
*
* @param string $use_default_embedding_model
* @param string $selected_embedding_provider
* @return object|null
*/
private function initialize_ai_instance($use_default_embedding_model, $selected_embedding_provider) {
/**
* Start with the main plugin provider,
* override if $use_default_embedding_model === 'no'
*/
$provider = $this->provider;
if ($use_default_embedding_model === 'no') {
$provider = $selected_embedding_provider;
} else {
// Check if a specific model was configured in "wpaicg_main_embedding_model"
$main_embedding_model = get_option('wpaicg_main_embedding_model', '');
if (!empty($main_embedding_model)) {
list($provider, $model) = explode(':', $main_embedding_model, 2);
}
}
switch ($provider) {
case 'OpenAI':
return WPAICG_OpenAI::get_instance()->openai();
case 'Azure':
return WPAICG_AzureAI::get_instance()->azureai();
case 'Google':
return WPAICG_Google::get_instance();
default:
return null;
}
}
/**
* Execute a Qdrant vector search and retrieve the best matches.
*
* @param string $endpoint
* @param string $collection
* @param array $embedding
* @param int $limit
* @param string $apiKey
* @return array
*/
private function search_qdrant($endpoint, $collection, $embedding, $limit, $apiKey) {
$group_id_value = "default";
$queryData = [
'vector' => $embedding,
'limit' => $limit,
'filter' => [
'must' => [[
'key' => 'group_id',
'match' => ['value' => $group_id_value]
]]
]
];
$response = wp_safe_remote_post("{$endpoint}/collections/{$collection}/points/search", [
'method' => 'POST',
'headers' => [
'api-key' => $apiKey,
'Content-Type' => 'application/json'
],
'body' => json_encode($queryData)
]);
if (is_wp_error($response)) {
return ['data' => esc_html($response->get_error_message())];
}
$bodyContent = wp_remote_retrieve_body($response);
$body = json_decode($bodyContent, true);
if (isset($body['result']) && is_array($body['result'])) {
$data = array_reduce($body['result'], function ($carry, $match) {
$postContent = get_post($match['id'])->post_content ?? '';
return $carry . ($carry ? "\n" : '') . $postContent;
}, '');
return ['status' => 'success', 'data' => $data];
}
return ['data' => esc_html__('No matches found or error in Qdrant response.', 'gpt3-ai-content-generator')];
}
}
WPAICG_Playground::get_instance();
}