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 / claude / qcld-bot-claude.php

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

458 lines 20.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Claude AI
4 *
5 * @package Botmaster
6 * @since 1.0.0
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly.
11 }
12
13 if ( ! class_exists( 'qcld_wpclaude_addons' ) ) {
14
15 class qcld_wpclaude_addons {
16
17 private $id = 'Claude AI';
18 public $version = '1.0.0';
19 public $helper;
20 protected static $_instance = null;
21
22 public static function instance() {
23 if ( is_null( self::$_instance ) ) {
24 self::$_instance = new self();
25 }
26 return self::$_instance;
27 }
28
29 public function __construct() {
30 $this->includes();
31 add_action( 'wp_ajax_claude_response', array( $this, 'claude_response_callback' ) );
32 add_action( 'wp_ajax_nopriv_claude_response', array( $this, 'claude_response_callback' ) );
33 add_action( 'wp_ajax_qcld_stream_claude', array( $this, 'qcld_stream_claude_callback' ) );
34 add_action( 'wp_ajax_nopriv_qcld_stream_claude', array( $this, 'qcld_stream_claude_callback' ) );
35 add_action( 'wp_ajax_qcld_claude_settings_option', array( $this, 'qcld_claude_settings_option_callback' ) );
36
37 if ( is_admin() && ! empty( $_GET['page'] ) && ( ( $_GET['page'] == 'openai-panel_dashboard' ) || ( $_GET['page'] == 'openai-panel_file' ) || ( $_GET['page'] == 'wpbot_openAi' ) || ( $_GET['page'] == 'openai-panel_help' ) ) ) {
38 add_action( 'admin_enqueue_scripts', array( $this, 'qcld_wb_chatbot_claude_admin_scripts' ) );
39 }
40 }
41
42 public function qcld_wb_chatbot_claude_admin_scripts() {
43 wp_register_script(
44 'qcld-wp-chatbot-claude-admin-js',
45 QCLD_wpCHATBOT_PLUGIN_URL . 'includes/integration/claude/assets/js/qcld-wp-claude-admin.js',
46 array( 'jquery' ),
47 QCLD_wpCHATBOT_VERSION,
48 true
49 );
50
51 wp_localize_script(
52 'qcld-wp-chatbot-claude-admin-js',
53 'ajax_object',
54 array(
55 'ajax_url' => admin_url( 'admin-ajax.php' ),
56 'ajax_nonce' => wp_create_nonce( 'wp_chatbot' ),
57 )
58 );
59 wp_enqueue_script( 'qcld-wp-chatbot-claude-admin-js' );
60 }
61
62 public function includes() {
63 require_once QCLD_wpCHATBOT_PLUGIN_DIR_PATH . 'includes/Parsedown.php';
64 require_once QCLD_wpCHATBOT_PLUGIN_DIR_PATH . 'includes/class-common-function.php';
65 }
66
67 public function qcld_claude_settings_option_callback() {
68 $nonce = sanitize_text_field( $_POST['nonce'] );
69 if ( ! wp_verify_nonce( $nonce, 'wp_chatbot' ) || ! current_user_can( 'manage_options' ) ) {
70 wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Failed in Security check', 'chatbot' ) ) );
71 wp_die();
72 } else {
73 $claude_api_key = sanitize_text_field( $_POST['claude_api_key'] ?? '' );
74 $voyage_api_key = sanitize_text_field( $_POST['voyage_api_key'] ?? '' );
75 $claude_model = sanitize_text_field( $_POST['claude_model'] ?? '' );
76 $claude_enabled = sanitize_text_field( $_POST['claude_enabled'] );
77 $claude_system_content = sanitize_text_field( $_POST['claude_system_content'] ) ?? '';
78 $qcld_claude_page_suggestion_enabled = sanitize_text_field( $_POST['qcld_claude_page_suggestion_enabled'] );
79 $qcld_claude_append_content = sanitize_text_field( $_POST['qcld_claude_append_content'] ) ?? '';
80 $qcld_claude_prepend_content = sanitize_text_field( $_POST['qcld_claude_prepend_content'] ) ?? '';
81 $claude_rag_enabled = sanitize_text_field( $_POST['claude_rag_enabled'] ) ?? '';
82 $claude_stream_enabled = sanitize_text_field( $_POST['claude_stream_enabled'] ?? '0' );
83
84 update_option( 'qcld_claude_stream_enabled', $claude_stream_enabled );
85 if ( $claude_rag_enabled != '' ) { update_option( 'qcld_claude_rag_enabled', $claude_rag_enabled ); }
86 update_option( 'qcld_claude_api_key', $claude_api_key );
87 update_option( 'qcld_voyage_api_key', $voyage_api_key );
88 if ( $claude_model != '' ) { update_option( 'qcld_claude_model', $claude_model ); }
89 if ( $claude_system_content != '' ) { update_option( 'qcld_claude_system_content', $claude_system_content ); }
90 if ( $claude_enabled != '' ) { update_option( 'qcld_claude_enabled', $claude_enabled ); }
91 if ( $claude_enabled == '1' ) {
92 update_option( 'ai_enabled', 0 );
93 update_option( 'qcld_ollama_enabled', 0 );
94 update_option( 'qcld_openrouter_enabled', 0 );
95 update_option( 'qcld_gemini_enabled', 0 );
96 update_option( 'qcld_mistral_enabled', 0 );
97 update_option( 'qcld_grok_enabled', 0 );
98 }
99 update_option( 'qcld_claude_page_suggestion_enabled', $qcld_claude_page_suggestion_enabled );
100 if (isset($_POST['openai_post_type'])) update_option( 'qcld_openai_relevant_post', $_POST['openai_post_type'] );
101 update_option( 'qcld_claude_append_content', $qcld_claude_append_content );
102 update_option( 'qcld_claude_prepend_content', $qcld_claude_prepend_content );
103 }
104
105 // Test query
106 $claude_api_key = get_option( 'qcld_claude_api_key' );
107 $data = array(
108 'model' => get_option('qcld_claude_model') ? get_option('qcld_claude_model') : 'claude-3-5-sonnet-latest',
109 'max_tokens' => 100,
110 'messages' => array(
111 array('role' => 'user', 'content' => 'give a confirmation that you are Claude AI (a small response)')
112 )
113 );
114
115 $args = array(
116 'body' => json_encode($data),
117 'headers' => array(
118 'Content-Type' => 'application/json',
119 'x-api-key' => $claude_api_key,
120 'anthropic-version' => '2023-06-01'
121 ),
122 'timeout' => 60,
123 'sslverify' => true,
124 );
125 $api_url = 'https://api.anthropic.com/v1/messages';
126 $result = wp_remote_post($api_url, $args);
127 $result_body = json_decode(wp_remote_retrieve_body($result), true);
128
129 if( isset($result_body['error']) ) {
130 wp_send_json( array( 'status' => 'error', 'msg' => esc_html( $result_body['error']['message'] ) ) );
131 } elseif ( isset($result_body['content']) && is_array($result_body['content']) ) {
132 $text_content = '';
133 foreach ($result_body['content'] as $block) {
134 if (isset($block['type']) && $block['type'] === 'text' && !empty($block['text'])) {
135 $text_content .= $block['text'];
136 }
137 }
138 if (!empty($text_content)) {
139 wp_send_json( array( 'status' => 'success', 'msg' => esc_html( $text_content ) ) );
140 }
141 }
142 wp_die();
143 }
144
145 public function qcld_stream_claude_callback() {
146 if ( get_option( 'is_rate_limiting_enabled' ) == '1' ) { do_action( 'rate_limit_checker' ); }
147
148 if ( function_exists( 'apache_setenv' ) ) { @apache_setenv( 'no-gzip', 1 ); }
149 @ini_set( 'zlib.output_compression', 0 );
150 @ini_set( 'implicit_flush', 1 );
151 while ( ob_get_level() ) { ob_end_clean(); }
152 ob_implicit_flush( 1 );
153
154 header( 'Content-Type: text/event-stream' );
155 header( 'Cache-Control: no-cache' );
156 header( 'Connection: keep-alive' );
157 header( 'X-Accel-Buffering: no' );
158
159 $claude_api_key = get_option( 'qcld_claude_api_key' );
160 $message = isset( $_POST['message'] ) ? sanitize_text_field( wp_unslash( $_POST['message'] ) ) : '';
161
162 if ( empty( $claude_api_key ) || empty( $message ) ) {
163 echo "data: [ERROR] Missing API key or message\n\n";
164 flush(); wp_die();
165 }
166
167 $claude_model = get_option( 'qcld_claude_model' );
168 if ( empty( $claude_model ) || strpos( $claude_model, 'latest' ) !== false ) { $claude_model = 'claude-sonnet-4-6'; }
169
170 $history_html = isset( $_POST['wpwHistory'] ) ? wp_unslash( $_POST['wpwHistory'] ) : '';
171 $history = $this->qcld_parse_wpwhistory_to_json( $history_html );
172 $history = $this->qcld_limit_conversation_history( $history, 10 );
173
174 $system_content = get_option( 'qcld_claude_system_content' ) ? get_option( 'qcld_claude_system_content' ) : 'You are a helpful assistant.';
175 $page_title = isset( $_POST['page_title'] ) ? sanitize_text_field( $_POST['page_title'] ) : '';
176 $system_content = str_replace( '[page_title]', $page_title, $system_content );
177
178 $rag_context = '';
179 if ( get_option( 'qcld_claude_rag_enabled' ) == '1' ) {
180 $rag_context = Qcld_Bot_Rag()->run_rag_search( $message );
181 }
182 if ( ! empty( $rag_context ) && $rag_context != 'No knowledge base found.' ) {
183 $system_content .= "\n\nContext from Knowledge Base:\n" . $rag_context;
184 }
185
186 $contents = $history;
187 $contents[] = [ 'role' => 'user', 'content' => $message ];
188
189 $data = [
190 'model' => $claude_model,
191 'max_tokens' => 1024,
192 'system' => $system_content,
193 'messages' => $contents,
194 'stream' => true
195 ];
196
197 $api_url = 'https://api.anthropic.com/v1/messages';
198
199 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close -- SSE streaming requires cURL write callback.
200 $ch = curl_init( $api_url );
201 curl_setopt( $ch, CURLOPT_POST, true );
202 curl_setopt( $ch, CURLOPT_HTTPHEADER, [
203 'Content-Type: application/json',
204 'x-api-key: ' . $claude_api_key,
205 'anthropic-version: 2023-06-01'
206 ] );
207 curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( $data ) );
208 curl_setopt( $ch, CURLOPT_TIMEOUT, 120 );
209 curl_setopt( $ch, CURLOPT_WRITEFUNCTION, function( $ch, $chunk ) {
210 $lines = explode( "\n", $chunk );
211 foreach ( $lines as $line ) {
212 $line = trim( $line );
213 if ( strpos( $line, 'data: ' ) === 0 ) {
214 $json_str = substr( $line, 6 );
215 $decoded = json_decode( $json_str, true );
216 if ( isset($decoded['type']) && $decoded['type'] === 'message_stop' ) {
217 echo "data: [DONE]\n\n"; flush(); continue;
218 }
219 if ( isset($decoded['type']) && $decoded['type'] === 'content_block_delta' ) {
220 $text_delta = $decoded['delta']['text'];
221 $payload = json_encode( [ 'choices' => [ [ 'delta' => [ 'content' => $text_delta ] ] ] ] );
222 echo 'data: ' . $payload . "\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- SSE streaming raw JSON output
223 echo str_repeat( ' ', 1024 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- SSE streaming padding
224 flush();
225 }
226 }
227 }
228 return strlen( $chunk );
229 } );
230
231 curl_exec( $ch );
232 if ( curl_errno( $ch ) ) {
233 echo 'data: [ERROR] ' . esc_html( curl_error( $ch ) ) . "\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- curl_error is already escaped above
234 flush();
235 }
236 curl_close( $ch );
237 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close
238 do_action( 'qcld_openai_user_rate_cal', 1 );
239 exit;
240 }
241
242 public function claude_response_callback() {
243 if (get_option('is_rate_limiting_enabled') == '1') { do_action('rate_limit_checker'); }
244 $claude_api_key = get_option( 'qcld_claude_api_key' );
245 $keyword = isset($_POST['keyword']) ? $_POST['keyword'] : '';
246
247 $rag_context = "";
248 if (get_option('qcld_claude_rag_enabled') == '1') {
249 $rag_context = Qcld_Bot_Rag()->run_rag_search($keyword);
250 }
251
252 $relevant_pagelink = Qcld_WPBot_Common_Functions::qcpd_relevant_pagelink( $keyword );
253 $relevant_pagelink = array_slice( $relevant_pagelink, 0, 5, true );
254 $relevant_pagelinks = '';
255 if ( ( get_option( 'qcld_claude_page_suggestion_enabled' ) == '1' ) && count( $relevant_pagelink ) > 0 ) {
256 $relevant_post_link = maybe_unserialize( get_option( 'qlcd_wp_chatbot_relevant_post_link_openai' ) );
257 if ( is_array( $relevant_post_link[ get_wpbot_locale() ] ) ) {
258 $relevant_pagelinks = '<br><br><p><em>' . implode( '', $relevant_post_link[ get_wpbot_locale() ] ) . '</em><p>' . implode( '</br>', $relevant_pagelink );
259 } else {
260 $relevant_pagelinks = '<br><br><p><em>' . $relevant_post_link[ get_wpbot_locale() ] . '</em><p>' . implode( '</br>', $relevant_pagelink );
261 }
262 }
263
264 $Parsedown = new Qcld_Parsedown();
265
266 $claude_model = get_option( 'qcld_claude_model' );
267 if ( empty( $claude_model ) || strpos( $claude_model, 'latest' ) !== false ) { $claude_model = 'claude-sonnet-4-6'; }
268 $api_url = 'https://api.anthropic.com/v1/messages';
269
270 $page_title = isset($_POST['page_title']) ? sanitize_text_field($_POST['page_title']) : '';
271 $history = [];
272 if (!empty($_POST['ai_history'])) {
273 $parsed_history = json_decode(wp_unslash($_POST['ai_history']), true);
274 if (is_array($parsed_history)) {
275 foreach ($parsed_history as $h) {
276 if (isset($h['role']) && isset($h['content'])) {
277 $role = ($h['role'] === 'assistant') ? 'assistant' : 'user';
278 $history[] = [
279 'role' => $role,
280 'content' => sanitize_text_field($h['content'])
281 ];
282 }
283 }
284 }
285 } elseif (!empty($_POST['wpwHistory'])) {
286 $history_html = wp_unslash($_POST['wpwHistory']);
287 $history = $this->qcld_parse_wpwhistory_to_json($history_html);
288 }
289 $history = $this->qcld_limit_conversation_history($history, 10);
290
291 $system_content = get_option( 'qcld_claude_system_content' ) ? get_option( 'qcld_claude_system_content' ) : 'You are a helpful assistant.';
292 $system_content = str_replace('[page_title]', $page_title, $system_content);
293
294 if (!empty($page_title) && strpos($system_content, $page_title) === false) {
295 $system_content .= "\n\nThe user is currently browsing the page: \"" . $page_title . "\". Use this information to help them navigate or provide context-aware answers.";
296 }
297
298 if (!empty($rag_context) && $rag_context != "No knowledge base found.") {
299 $system_content .= "\n\nContext from Knowledge Base:\n" . $rag_context;
300 }
301
302 $action_prompt = !empty($_POST['action_prompt']) ? wp_unslash($_POST['action_prompt']) : '';
303 if (!empty($action_prompt)) {
304 $system_content .= "\n\nCRITICAL ACTIVE ACTION INSTRUCTION:\n" . $action_prompt;
305 }
306
307 $saved_ai_forms = get_option('wpbot_ai_forms', array());
308 if (!empty($saved_ai_forms) && is_array($saved_ai_forms)) {
309 $system_content .= "\n\nYou must handle the following interactive forms when the user asks for them:\n";
310 foreach ($saved_ai_forms as $form) {
311 $system_content .= "\nForm Title: " . $form['title'] . "\nInstructions: " . $form['prompt'] . "\n";
312 }
313 $system_content .= "\n\nCRITICAL INSTRUCTIONS FOR INTERACTIVE FORMS:\n";
314 $system_content .= "When a user triggers an interactive form, you must act as a step-by-step data collection agent.\n";
315 $system_content .= "1. DO NOT ask all questions at once. Ask exactly ONE question at a time.\n";
316 $system_content .= "2. Wait for the user's response before asking the next question.\n";
317 $system_content .= "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";
318 $system_content .= "__AI_FORM_DATA__{ \"form_title\": \"<Form Title>\", \"data\": { \"Question 1\": \"Answer 1\", \"Question 2\": \"Answer 2\" } }__AI_FORM_DATA_END__\n";
319 $system_content .= "Do not include any other text after this JSON block once the form is complete.\n";
320 $system_content .= "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";
321 $system_content .= "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.";
322 }
323
324 $contents = $history;
325 $last_msg = end($contents);
326 if (!$last_msg || $last_msg['role'] !== 'user' || $last_msg['content'] !== $keyword) {
327 $contents[] = [ 'role' => 'user', 'content' => $keyword ];
328 }
329
330 $data = array(
331 'model' => $claude_model,
332 'max_tokens' => 1024,
333 'system' => $system_content,
334 'messages' => $contents
335 );
336
337 $max_attempts = 3;
338 $attempt = 0;
339 $response = ['status' => 'error', 'message' => 'Unknown error'];
340
341 while ($attempt < $max_attempts) {
342 $attempt++;
343 $args = array(
344 'body' => json_encode($data),
345 'headers' => array(
346 'Content-Type' => 'application/json',
347 'x-api-key' => $claude_api_key,
348 'anthropic-version' => '2023-06-01'
349 ),
350 'timeout' => 60,
351 'sslverify' => true,
352 );
353
354 $result = wp_remote_post($api_url, $args);
355
356 if ( is_wp_error( $result ) ) {
357 $response['status'] = 'error';
358 $response['message'] = 'WP_Error: ' . $result->get_error_message();
359 break;
360 }
361
362 $http_code = wp_remote_retrieve_response_code($result);
363 $body = wp_remote_retrieve_body($result);
364 $msg = json_decode($body, true);
365
366 if (isset($msg['content']) && is_array($msg['content'])) {
367 $text_content = '';
368 foreach ($msg['content'] as $block) {
369 if (isset($block['type']) && $block['type'] === 'text' && !empty($block['text'])) {
370 $text_content .= $block['text'];
371 }
372 }
373 if (!empty($text_content)) {
374 $response['status'] = 'success';
375 $reply_text = $Parsedown->text( $text_content );
376 if (strpos($reply_text, 'AI_FORM_DATA') !== false) {
377 $reply_text = Qcld_WPBot_Common_Functions::format_and_save_ai_form_response($reply_text);
378 $response['message'] = $reply_text;
379 } else {
380 $response['message'] = $reply_text . $relevant_pagelinks;
381 }
382 break;
383 }
384 }
385
386 if (($http_code == 503 || $http_code == 429) && $attempt < $max_attempts) {
387 $wait_seconds = $attempt * 2;
388 sleep($wait_seconds);
389 continue;
390 }
391
392 $response['status'] = 'error';
393 if ( isset( $msg['error']['message'] ) ) {
394 $response['message'] = 'Claude API Error: ' . $msg['error']['message'];
395 } else {
396 $response['message'] = 'Invalid response format from Claude API';
397 }
398 $response['raw_response'] = $body;
399 break;
400 }
401 do_action('qcld_openai_user_rate_cal', 1);
402 echo json_encode( $response );
403 wp_die();
404 }
405
406 public function qcld_parse_wpwhistory_to_json($html) {
407 if (empty($html)) return [];
408 $messages = [];
409 preg_match_all('/<li[^>]*>(.*?)<\/li>/is', $html, $matches);
410 if (empty($matches[0])) return [];
411
412 foreach ($matches[0] as $li_html) {
413 $is_assistant = (strpos($li_html, 'wp-chatbot-msg') !== false && strpos($li_html, 'wp-chatbot-agent') !== false);
414 $content = '';
415 if (preg_match('/<div[^>]*class=["\'][^"\']*wp-chatbot-paragraph[^"\']*["\'][^>]*>(.*?)<\/div>/is', $li_html, $p_matches)) {
416 $content = $p_matches[1];
417 } else if (preg_match('/<div[^>]*class=["\'][^"\']*wpw-chatbot-user-msg[^"\']*["\'][^>]*>(.*?)<\/div>/is', $li_html, $u_matches)) {
418 $content = $u_matches[1];
419 } else {
420 $clean_li = preg_replace('/<div[^>]*class=["\'][^"\']*wp-chatbot-avatar[^"\']*["\'][^>]*>.*?<\/div>/is', '', $li_html);
421 $clean_li = preg_replace('/<div[^>]*class=["\'][^"\']*wp-chatbot-agent[^"\']*["\'][^>]*>.*?<\/div>/is', '', $clean_li);
422 $content = wp_strip_all_tags( $clean_li );
423 }
424
425 $content = preg_replace('/<br\s*\/?>/i', "\n", $content);
426 $content = preg_replace('/<div[^>]*class=["\'][^"\']*relevant-links[^"\']*["\'][^>]*>.*?<\/div>/is', '', $content);
427 $content = preg_replace('/<span[^>]*class=["\'][^"\']*qcld-chatbot-wildcard[^"\']*["\'][^>]*>.*?<\/span>/is', '', $content);
428 $content = trim( wp_strip_all_tags( $content ) );
429 $content = html_entity_decode($content, ENT_QUOTES, 'UTF-8');
430
431 if (!empty($content)) {
432 $messages[] = [
433 'role' => $is_assistant ? 'assistant' : 'user', // Claude uses 'assistant'
434 'content' => $content
435 ];
436 }
437 }
438 return $messages;
439 }
440
441 public function qcld_limit_conversation_history($messages, $max_messages = 10) {
442 if (count($messages) > $max_messages) {
443 return array_slice($messages, -$max_messages);
444 }
445 return $messages;
446 }
447 }
448
449 if ( ! function_exists( 'qcld_wpclaude_addons' ) ) {
450 function qcld_wpclaude_addons() {
451 $qcld_wpclaude_addon = new qcld_wpclaude_addons();
452 return $qcld_wpclaude_addon->instance();
453 }
454 }
455
456 qcld_wpclaude_addons();
457 }
458