PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.8.1
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.8.1
8.8.1 8.8.0 8.7.9 8.7.8 8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 8.5.6 All 537 releases
chatbot / includes / integration / openrouter / qcld-bot-openrouter.php

qcld-bot-openrouter.php in WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services 8.8.1, at includes/integration/openrouter/qcld-bot-openrouter.php

495 lines 25.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) exit; // Exit if accessed directly
3
4 if(!class_exists('qcld_wpopenrouter_addons')){
5
6
7 /**
8 * Main Class.
9 */
10 final class qcld_wpopenrouter_addons
11 {
12 private $id = 'Open AI';
13
14 /**
15 * WPBot Pro version.
16 *
17 * @var string
18 */
19 public $version = '1.0.6';
20
21 /**
22 * WPBot Pro helper.
23 *
24 * @var object
25 */
26 public $helper;
27
28 /**
29 * The single instance of the class.
30 *
31 * @var qcld_wb_Chatbot
32 * @since 1.0.0
33 */
34 protected static $_instance = null;
35
36 /**
37 * Main wpbot Instance.
38 *
39 * Ensures only one instance of wpbot is loaded or can be loaded.
40 *
41 * @return qcld_wb_Chatbot - Main instance.
42 * @since 1.0.0
43 * @static
44 */
45 public static function instance() {
46 if ( is_null( self::$_instance ) ) {
47 self::$_instance = new self();
48 }
49
50 return self::$_instance;
51 }
52
53 public $response_list;
54
55 /**
56 * Constructor
57 */
58 public function __construct()
59 {
60
61 $this->includes();
62 add_action('wp_ajax_openai_save_assistant', [$this, 'save_assistant_callback']);
63 add_action('wp_ajax_openrouter_response',[$this,'openrouter_response_callback']);
64 add_action('wp_ajax_nopriv_openrouter_response', [$this, 'openrouter_response_callback']);
65 add_action('wp_ajax_qcld_openrouter_settings_option',[$this,'qcld_openrouter_settings_option_callback']);
66
67 add_action('wp_ajax_update_settings_option', [$this, 'qcld_update_settings_option_callback']);
68
69 if (is_admin() && !empty($_GET["page"]) && (($_GET["page"] == "openai-panel_dashboard") || ($_GET["page"] == "openai-panel_file") || ($_GET["page"] == "openai-panel_help"))) {
70 add_action('admin_enqueue_scripts', array($this, 'qcld_wb_chatbot_admin_scripts'));
71 }
72 //add_action('wp_enqueue_scripts', array($this, 'qcld_wb_chatbot_openrouter_scripts'));
73 add_action('admin_enqueue_scripts', array($this, 'qcld_wb_chatbot_openrouter_admin_scripts'));
74 }
75
76
77
78 public function qcld_wb_chatbot_openrouter_admin_scripts() {
79 if ( ! current_user_can( 'manage_options' ) ) {
80 return ;
81 }
82 wp_register_script(
83 'qcld-wp-chatbot-openrouter-admin-js',
84 QCLD_wpCHATBOT_PLUGIN_URL . 'includes/integration/openrouter/assets/js/qcld-wp-openrouter-admin.js',
85 array('jquery'),
86 QCLD_wpCHATBOT_VERSION,
87 true
88 );
89
90 // Localize the script with necessary data
91 wp_localize_script('qcld-wp-chatbot-openrouter-admin-js', 'qcld_gemini_admin_data', array(
92 'ajax_url' => admin_url('admin-ajax.php'),
93 'ajax_nonce' => wp_create_nonce('wp_chatbot'),
94 'openrouter_api_key' => get_option('qcld_openrouter_api_key'),
95 'openrouter_model' => get_option('qcld_openrouter_model'),
96 'openrouter_enabled' => get_option('qcld_openrouter_enabled'),
97 'qcld_openrouter_append_content' => get_option('qcld_openrouter_append_content'),
98 'qcld_openrouter_prepend_content' => get_option('qcld_openrouter_prepend_content')
99 ));
100
101 wp_enqueue_script('qcld-wp-chatbot-openrouter-admin-js');
102 }
103
104 /**
105 * Define wpbot Constants.
106 *
107 * @return void
108 * @since 1.0.0
109 */
110 public function includes() {
111 require_once( QCLD_wpCHATBOT_PLUGIN_DIR_PATH . "includes/Parsedown.php" );
112 require_once( QCLD_wpCHATBOT_PLUGIN_DIR_PATH . "includes/class-common-function.php" );
113 }
114 public function qcld_openrouter_settings_option_callback() {
115 $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
116 if (!wp_verify_nonce($nonce, 'wp_chatbot')) {
117 wp_send_json(array('success' => false, 'msg' => esc_html__('Failed in Security check', 'chatbot')));
118 wp_die();
119 } elseif ( ! current_user_can( 'manage_options' ) ) {
120 wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Unauthorized user', 'chatbot' ) ) );
121 wp_die();
122 } else {
123 if (get_option('is_rate_limiting_enabled') == '1') {
124 do_action('rate_limit_checker');
125 }
126 $openrouter_api_key = sanitize_text_field(wp_unslash($_POST['openrouter_api_key']));
127 $openrouter_model = sanitize_text_field(wp_unslash($_POST['openrouter_model']));
128 $openrouter_enabled = sanitize_text_field(wp_unslash($_POST['openrouter_enabled']));
129 $qcld_openrouter_page_suggestion_enabled = sanitize_text_field(wp_unslash($_POST['qcld_openrouter_page_suggestion_enabled']));
130 $opnrouter_is_context_awareness_enabled = sanitize_text_field(wp_unslash($_POST['opnrouter_is_context_awareness_enabled']));
131 $qcld_openrouter_append_content = sanitize_text_field(wp_unslash($_POST['qcld_openrouter_append_content'])) ?? '';
132 $qcld_openrouter_prepend_content = sanitize_text_field(wp_unslash($_POST['qcld_openrouter_prepend_content'])) ?? '';
133 if($openrouter_api_key != '') {
134 update_option('qcld_openrouter_api_key', $openrouter_api_key);
135 }
136 if($openrouter_model != '') {
137 update_option('qcld_openrouter_model', $openrouter_model);
138 }
139 if($openrouter_enabled != '') {
140 update_option('qcld_openrouter_enabled', $openrouter_enabled);
141 }
142 if($openrouter_enabled == '1') {
143 update_option('ai_enabled', 0);
144 update_option('qcld_gemini_enabled', 0);
145 update_option('qcld_grok_enabled', 0);
146 update_option('disable_wp_chatbot_site_search',1);
147 }
148 update_option('qcld_openrouter_page_suggestion_enabled', $qcld_openrouter_page_suggestion_enabled);
149 update_option('opnrouter_context_awareness_enabled', $opnrouter_is_context_awareness_enabled);
150 $openai_post_types = array();
151 if (isset($_POST['openai_post_type'])) {
152 $raw_post_types = wp_unslash($_POST['openai_post_type']);
153 if (is_array($raw_post_types)) {
154 $openai_post_types = array_map('sanitize_text_field', $raw_post_types);
155 } else {
156 $openai_post_types = sanitize_text_field($raw_post_types);
157 }
158 }
159 $is_page_rag_enabled = sanitize_text_field(wp_unslash($_POST['is_page_rag_enabled']));
160 update_option('qcld_openai_relevant_post', $openai_post_types);
161 update_option('is_page_rag_enabled', $is_page_rag_enabled);
162
163 update_option('qcld_openrouter_append_content', $qcld_openrouter_append_content);
164 update_option('qcld_openrouter_prepend_content', $qcld_openrouter_prepend_content);
165
166 // Add the check query
167 $openrouter_api_key = get_option('qcld_openrouter_api_key');
168 $openrouter_model = get_option('qcld_openrouter_model') ? get_option('qcld_openrouter_model') : 'openai/gpt-3.5-turbo';
169
170 $messages = array();
171 $messages[] = array(
172 'role' => 'user',
173 'content' => 'give a confirmation that you are an AI (in a small response)?'
174 );
175
176 $data = wp_json_encode(array(
177 'model' => $openrouter_model,
178 'messages' => $messages
179 ));
180
181 $api_url = 'https://openrouter.ai/api/v1/chat/completions';
182
183 $args = array(
184 'body' => $data,
185 'headers' => array(
186 'Content-Type' => 'application/json',
187 'Authorization' => 'Bearer ' . $openrouter_api_key
188 ),
189 'timeout' => 60,
190 'redirection' => 5,
191 'blocking' => true,
192 'httpversion' => '1.0',
193 'sslverify' => true,
194 );
195
196 $result = wp_remote_post($api_url, $args);
197
198 if (is_wp_error($result)) {
199 /* translators: %s: API error message. */
200 wp_send_json( array( 'status' => 'error', 'msg' => esc_html( sprintf( __( 'API request failed: %s', 'chatbot' ), $result->get_error_message() ) ) ) );
201 } else {
202 $http_code = wp_remote_retrieve_response_code($result);
203 $response_body = wp_remote_retrieve_body($result);
204 $msg = json_decode($response_body, true);
205 do_action('qcld_openai_user_rate_cal', 1);
206 if ($http_code === 200 && isset($msg['choices'][0]['message']['content'])) {
207 wp_send_json( array( 'status' => 'success', 'msg' => esc_html( $msg['choices'][0]['message']['content'] ) ) );
208 } else {
209 $error_message = isset($msg['error']['message']) ? $msg['error']['message'] : 'Invalid API setup or API request failed.';
210 wp_send_json( array( 'status' => 'error', 'msg' => esc_html( $error_message ) ) );
211 }
212 }
213 wp_die();
214 }
215 // echo wp_json_encode($openrouter_enabled);
216 wp_die();
217 }
218 public function openrouter_response_callback(){
219 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'wp_chatbot' ) ) {
220 wp_send_json_error([
221 'status' => 'error',
222 'message' => esc_html__( 'Security check failed. Unauthorized request.', 'chatbot' )
223 ]);
224 wp_die();
225 }
226
227 $openrouter_model = get_option('qcld_openrouter_model');
228 $openrouter_api_key = get_option('qcld_openrouter_api_key');
229 $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field( wp_unslash( $_POST['keyword'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
230 $relevant_pagelink = Qcld_WPBot_Common_Functions::qcpd_relevant_pagelink($keyword);
231 $relevant_pagelink = array_slice($relevant_pagelink, 0, 5, true);
232
233 // Build context-aware messages with system instructions
234 $messages = array();
235
236 // Add system message with context if enabled
237 if ( get_option('opnrouter_context_awareness_enabled') == '1' ) {
238 $site_name = get_bloginfo('name');
239 $site_desc = get_bloginfo('description');
240
241 // Get current page URL and title more reliably
242 $current_url = '';
243 $page_title = '';
244 $page_summary = '';
245
246 // Try to get from referrer first
247 $ref = wp_get_referer();
248 if ( ! $ref && isset($_SERVER['HTTP_REFERER']) ) {
249 $ref = esc_url_raw( $_SERVER['HTTP_REFERER'] );
250 }
251
252 if ( $ref ) {
253 $current_url = $ref;
254
255 // Try to get post/page by URL
256 $post_id = url_to_postid( $ref );
257 if ( $post_id ) {
258 $page_title = get_the_title( $post_id );
259 $raw_content = get_post_field( 'post_content', $post_id );
260 $text_content = wp_strip_all_tags( $raw_content );
261 $page_summary = wp_trim_words( $text_content, 120, '…' );
262 } else {
263 // If not a post/page, try to extract title from URL or use current page
264 $parsed_url = wp_parse_url( $ref );
265 if ( isset($parsed_url['path']) ) {
266 $path = trim($parsed_url['path'], '/');
267 if ( ! empty($path) ) {
268 // Try to get title from current page if we're on it
269 if ( is_singular() ) {
270 $page_title = get_the_title();
271 $raw_content = get_the_content();
272 $text_content = wp_strip_all_tags( $raw_content );
273 $page_summary = wp_trim_words( $text_content, 120, '…' );
274 } elseif ( is_archive() ) {
275 $page_title = get_the_archive_title();
276 } elseif ( is_search() ) {
277 $page_title = 'Search Results';
278 } elseif ( is_404() ) {
279 $page_title = 'Page Not Found';
280 }
281 }
282 }
283 }
284 } else {
285 // Fallback to current page info
286 $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http");
287 $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field($_SERVER['HTTP_HOST']) : '';
288 $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field($_SERVER['REQUEST_URI']) : '';
289 $current_url = esc_url_raw($scheme . '://' . $host . $request_uri);
290
291 if ( is_singular() ) {
292 $page_title = get_the_title();
293 $raw_content = get_the_content();
294 $text_content = wp_strip_all_tags( $raw_content );
295 $page_summary = wp_trim_words( $text_content, 120, '…' );
296 } elseif ( is_archive() ) {
297 $page_title = get_the_archive_title();
298 } elseif ( is_search() ) {
299 $page_title = 'Search Results';
300 } elseif ( is_404() ) {
301 $page_title = 'Page Not Found';
302 }
303 }
304
305 $context_bits = array();
306 if ( $site_name ) { $context_bits[] = 'Site: ' . $site_name; }
307 if ( $site_desc ) { $context_bits[] = 'Tagline: ' . $site_desc; }
308 if ( $page_title ) { $context_bits[] = 'Page title: ' . $page_title; }
309 if ( $current_url ) { $context_bits[] = 'URL: ' . $current_url; }
310 if ( $page_summary ) { $context_bits[] = 'Page summary: ' . $page_summary; }
311
312 if ( ! empty( $context_bits ) ) {
313 $context_info = 'Context Information: ' . implode( '. ', $context_bits ) . '. Please use this context to provide more relevant and accurate responses.';
314
315 // Add system message with context
316 $messages[] = array(
317 'role' => 'system',
318 'content' => $context_info
319 );
320 }
321 }
322
323 // RAG Integration
324 if (get_option('is_page_rag_enabled') == '1') {
325 $rag_context_text = Qcld_Bot_Rag::instance()->run_rag_search($keyword);
326 if (!empty($rag_context_text) && $rag_context_text != "No knowledge base found.") {
327 $rag_context = "Relevant Knowledge Base Information:\n";
328 $rag_context .= $rag_context_text;
329 $rag_context .= "\n\nUse the above information to answer the user's question. If the answer is not in the Knowledge Base, rely on your general knowledge but mention that this information is not in the local knowledge base.";
330
331 $messages[] = array(
332 'role' => 'system',
333 'content' => $rag_context
334 );
335 }
336 }
337
338 $action_prompt = !empty($_POST['action_prompt']) ? wp_unslash($_POST['action_prompt']) : '';
339 if (!empty($action_prompt)) {
340 $messages[] = array(
341 'role' => 'system',
342 'content' => "CRITICAL ACTIVE ACTION INSTRUCTION:\n" . $action_prompt
343 );
344 }
345
346 $saved_ai_forms = get_option('wpbot_ai_forms', array());
347 if (!empty($saved_ai_forms) && is_array($saved_ai_forms)) {
348 $forms_instruction = "You must handle the following interactive forms when the user asks for them:\n";
349 foreach ($saved_ai_forms as $form) {
350 $forms_instruction .= "\nForm Title: " . $form['title'] . "\nInstructions: " . $form['prompt'] . "\n";
351 }
352 $forms_instruction .= "\n\nCRITICAL INSTRUCTIONS FOR INTERACTIVE FORMS:\n";
353 $forms_instruction .= "When a user triggers an interactive form, you must act as a step-by-step data collection agent.\n";
354 $forms_instruction .= "1. DO NOT ask all questions at once. Ask exactly ONE question at a time.\n";
355 $forms_instruction .= "2. Wait for the user's response before asking the next question.\n";
356 $forms_instruction .= "3. Once all necessary information is collected for the form, you MUST output a final JSON block summarizing the collected data. The keys inside the \"data\" object MUST be dynamically named based on the specific questions you asked during the form collection (e.g., \"Full Name\", \"Company Size\", \"Email\", etc.). The final JSON block must be wrapped EXACTLY in these delimiters:\n";
357 $forms_instruction .= "__AI_FORM_DATA__{ \"form_title\": \"<Form Title>\", \"data\": { \"Question 1\": \"Answer 1\", \"Question 2\": \"Answer 2\" } }__AI_FORM_DATA_END__\n";
358 $forms_instruction .= "Do not include any other text after this JSON block once the form is complete.\n";
359 $forms_instruction .= "4. If the user provides an invalid, irrelevant, or nonsensical answer to your question, DO NOT apologize or state that you lack information. Instead, politely inform the user in the language of the conversation (the language the user is speaking in, e.g. German if communicating in German) that their answer is invalid or cannot be assigned, and ask the exact same question again in the user's language. NEVER output the English phrase 'Invalid answer found' unless the user is communicating in English.\n";
360 $forms_instruction .= "5. MULTI-LANGUAGE SUPPORT: Always communicate, ask questions, validate answers, and reply strictly in the user's language (matching the language used by the user, e.g. German, French, Spanish, etc.). Understand and accept valid answers in the user's language.";
361 $messages[] = array(
362 'role' => 'system',
363 'content' => $forms_instruction
364 );
365 }
366
367 if (!empty($_POST['ai_history'])) {
368 $parsed_history = json_decode(wp_unslash($_POST['ai_history']), true);
369 if (is_array($parsed_history)) {
370 foreach ($parsed_history as $h) {
371 if (isset($h['role']) && isset($h['content'])) {
372 $role = ($h['role'] === 'assistant') ? 'assistant' : 'user';
373 $messages[] = array(
374 'role' => $role,
375 'content' => sanitize_text_field($h['content'])
376 );
377 }
378 }
379 }
380 }
381
382 $last_msg = end($messages);
383 if (!$last_msg || $last_msg['role'] !== 'user' || $last_msg['content'] !== $keyword) {
384 $messages[] = array(
385 'role' => 'user',
386 'content' => $keyword
387 );
388 }
389
390 if( (get_option('page_suggestion_enabled') == '1') && count($relevant_pagelink) > 0 ){
391 $relevant_post_link = maybe_unserialize(get_option('qlcd_wp_chatbot_relevant_post_link_openai'));
392 $locale = ( function_exists('get_locale') ) ? get_locale() : 'en_US';
393 if ( isset($relevant_post_link[$locale]) && is_array($relevant_post_link[$locale]) ) {
394 $relevant_pagelinks = '<br><br><p><em>' . implode('', $relevant_post_link[$locale]) . '</em><p>' . implode("</br>", $relevant_pagelink);
395 } elseif ( isset($relevant_post_link[$locale]) ) {
396 $relevant_pagelinks = '<br><br><p><em>' . $relevant_post_link[$locale] . '</em><p>' . implode("</br>", $relevant_pagelink);
397 } else {
398 $relevant_pagelinks = '<br><br><p><em></em><p>' . implode("</br>", $relevant_pagelink);
399 }
400 } else {
401 $relevant_pagelinks = '';
402 }
403 $Qcld_Parsedown = new Qcld_Parsedown();
404 $data = wp_json_encode(array(
405 'model' => $openrouter_model,
406 'messages' => $messages
407 ));
408
409 $api_url = 'https://openrouter.ai/api/v1/chat/completions';
410
411 // Use WordPress wp_remote_post for better error handling and consistency
412 $args = array(
413 'body' => $data,
414 'headers' => array(
415 'Content-Type' => 'application/json',
416 'Authorization' => 'Bearer ' . $openrouter_api_key
417 ),
418 'timeout' => 60,
419 'redirection' => 5,
420 'blocking' => true,
421 'httpversion' => '1.0',
422 'sslverify' => true,
423 );
424
425 $result = wp_remote_post($api_url, $args);
426
427 // Check for WordPress errors
428 if (is_wp_error($result)) {
429 $response['status'] = 'error';
430 $response['message'] = 'API request failed: ' . $result->get_error_message();
431 } else {
432 $http_code = wp_remote_retrieve_response_code($result);
433 $response_body = wp_remote_retrieve_body($result);
434
435 if ($http_code === 200) {
436 $msg = json_decode($response_body);
437 if(isset($msg->choices[0]->message->content)) {
438 $response['status'] = 'success';
439 $reply_text = $Qcld_Parsedown->text($msg->choices[0]->message->content);
440 if (strpos($reply_text, 'AI_FORM_DATA') !== false) {
441 $reply_text = Qcld_WPBot_Common_Functions::format_and_save_ai_form_response($reply_text);
442 $response['message'] = $reply_text;
443 } else {
444 $response['message'] = $reply_text . $relevant_pagelinks;
445 }
446 } else {
447 $response['status'] = 'error';
448 $response['message'] = 'Sorry, I encountered an error processing your AI request. Please check api key and try again later.';
449 }
450 } else {
451 $response['status'] = 'error';
452 $response['message'] = 'API request failed with HTTP code: ' . $http_code;
453 }
454 }
455 wp_send_json( $response );
456 }
457 public function qcld_update_settings_option_callback(){
458 // Verify nonce for CSRF protection
459 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
460 if (!wp_verify_nonce($nonce, 'wp_chatbot')) {
461 wp_send_json_error(array('message' => esc_html__('Security check failed', 'chatbot')));
462 wp_die();
463 }
464
465 // Check user capability - only administrators can modify settings
466 if (!current_user_can('manage_options')) {
467 wp_send_json_error(array('message' => esc_html__('Unauthorized access', 'chatbot')));
468 wp_die();
469 }
470
471 // Proceed with option updates
472 update_option('disable_wp_chatbot_site_search', 1);
473 update_option('enable_wp_chatbot_post_content', '');
474
475 // Send success response
476 wp_send_json_success(array('message' => esc_html__('Settings updated successfully', 'chatbot')));
477 wp_die();
478 }
479 }
480
481 /**
482 * @return qcld_wpopenai_addon
483 */
484 if(!function_exists('qcld_wpopenrouter_addons')){
485 function qcld_wpopenrouter_addons() {
486 $qcld_wpopenrouter_addon = new qcld_wpopenrouter_addons();
487 return $qcld_wpopenrouter_addon->instance();
488
489 }
490 }
491
492 //fire off the plugin
493 qcld_wpopenrouter_addons();
494
495 }