PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.4.2
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.4.2
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / modules / chatbot.php
ai-engine / classes / modules Last commit date
advisor.php 2 years ago chatbot.php 2 years ago discussions.php 2 years ago files.php 2 years ago security.php 2 years ago tasks.php 2 years ago utilities.php 2 years ago wand.php 2 years ago
chatbot.php
605 lines
1 <?php
2
3 // Params for the chatbot (front and server)
4
5 define( 'MWAI_CHATBOT_FRONT_PARAMS', [ 'id', 'customId', 'aiName', 'userName', 'guestName',
6 'textSend', 'textClear', 'imageUpload', 'fileSearch',
7 'textInputPlaceholder', 'textInputMaxLength', 'textCompliance', 'startSentence', 'localMemory',
8 'themeId', 'window', 'icon', 'iconText', 'iconTextDelay', 'iconAlt', 'iconPosition', 'iconBubble',
9 'fullscreen', 'copyButton'
10 ] );
11 define( 'MWAI_CHATBOT_SERVER_PARAMS', [ 'id', 'envId', 'scope', 'mode', 'contentAware', 'context',
12 'embeddingsEnvId', 'embeddingsIndex', 'embeddingsNamespace', 'assistantId', 'instructions',
13 'model', 'temperature', 'maxTokens', 'contextMaxLength', 'maxResults', 'apiKey', 'functions'
14 ] );
15
16 // Params for the discussions (front and server)
17
18 define( 'MWAI_DISCUSSIONS_FRONT_PARAMS', [ 'themeId', 'textNewChat' ] );
19 define( 'MWAI_DISCUSSIONS_SERVER_PARAMS', [ 'customId' ] );
20
21 class Meow_MWAI_Modules_Chatbot {
22 private $core = null;
23 private $namespace = 'mwai-ui/v1';
24 private $siteWideChatId = null;
25
26 public function __construct() {
27 global $mwai_core;
28 $this->core = $mwai_core;
29 add_shortcode( 'mwai_chatbot', array( $this, 'chat_shortcode' ) );
30 add_shortcode( 'mwai_chatbot_v2', array( $this, 'chat_shortcode' ) );
31 add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
32 $this->siteWideChatId = $this->core->get_option( 'botId' );
33 add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) );
34 add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) );
35
36 if ( $this->core->get_option( 'chatbot_discussions' ) ) {
37 add_shortcode( 'mwai_discussions', [ $this, 'chatbot_discussions' ] );
38 }
39 }
40
41 public function register_scripts() {
42
43 // Load JS
44 $physical_file = trailingslashit( MWAI_PATH ) . 'app/chatbot.js';
45 $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : MWAI_VERSION;
46 wp_register_script( 'mwai_chatbot', trailingslashit( MWAI_URL )
47 . 'app/chatbot.js', [ 'wp-element' ], $cache_buster, false );
48
49 // Load CSS for the Themes
50 $themes = $this->core->get_themes();
51 foreach ( $themes as $theme ) {
52 if ( $theme['type'] === 'internal' ) {
53 $themeId = $theme['themeId'];
54 $filename = $themeId . '.css';
55 $physical_file = trailingslashit( MWAI_PATH ) . 'themes/' . $filename;
56 $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : MWAI_VERSION;
57 wp_register_style( 'mwai_chatbot_theme_' . $themeId, trailingslashit( MWAI_URL )
58 . 'themes/' . $filename, [], $cache_buster );
59 }
60 }
61
62 // Actual loading of the scripts
63 $hasSiteWideChat = $this->siteWideChatId && $this->siteWideChatId !== 'none';
64 if ( is_admin() || $hasSiteWideChat ) {
65 $this->enqueue_scripts();
66 if ( $hasSiteWideChat ) {
67 // Chatbot Injection
68 add_action( 'wp_footer', array( $this, 'inject_chat' ) );
69 }
70 }
71 }
72
73 public function enqueue_scripts() {
74 // TODO: We should optimize and only load the themes that are used.
75 $themes = $this->core->get_themes();
76 foreach ( $themes as $theme ) {
77 if ( $theme['type'] === 'internal' ) {
78 $themeId = $theme['themeId'];
79 wp_enqueue_style( "mwai_chatbot_theme_$themeId" );
80 }
81 }
82 wp_enqueue_script( "mwai_chatbot" );
83 if ( $this->core->get_option( 'syntax_highlight' ) ) {
84 wp_enqueue_script( "mwai_highlight" );
85 }
86 }
87
88 public function rest_api_init() {
89 register_rest_route( $this->namespace, '/chats/submit', array(
90 'methods' => 'POST',
91 'callback' => [ $this, 'rest_chat' ],
92 'permission_callback' => array( $this->core, 'check_rest_nonce' )
93 ) );
94 }
95
96 public function basics_security_check( $botId, $customId, $newMessage, $newFileId ) {
97 if ( !$botId && !$customId ) {
98 $this->core->log( "⚠️ The query was rejected - no botId nor id was specified.");
99 return false;
100 }
101
102 if ( $newFileId ) {
103 return true;
104 }
105
106 $length = strlen( empty( $newMessage ) ? "" : $newMessage );
107 if ( $length < 1 || $length > ( 4096 * 16 ) ) {
108 $this->core->log( "⚠️ The query was rejected - message was too short or too long.");
109 return false;
110 }
111 return true;
112 }
113
114 public function rest_chat( $request ) {
115 $params = $request->get_json_params();
116 $botId = $params['botId'] ?? null;
117 $customId = $params['customId'] ?? null;
118 $stream = $params['stream'] ?? false;
119 $newMessage = trim( $params['newMessage'] ?? '' );
120 $newFileId = $params['newFileId'] ?? null;
121
122 if ( !$this->basics_security_check( $botId, $customId, $newMessage, $newFileId )) {
123 return new WP_REST_Response( [
124 'success' => false,
125 'message' => apply_filters( 'mwai_ai_exception', 'Sorry, your query has been rejected.' )
126 ], 403 );
127 }
128
129 try {
130 $data = $this->chat_submit( $botId, $newMessage, $newFileId, $params, $stream );
131 return new WP_REST_Response( [
132 'success' => true,
133 'reply' => $data['reply'],
134 'images' => $data['images'],
135 'usage' => $data['usage']
136 ], 200 );
137 }
138 catch ( Exception $e ) {
139 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
140 return new WP_REST_Response( [
141 'success' => false,
142 'message' => $message
143 ], 500 );
144 }
145 }
146
147 public function chat_submit( $botId, $newMessage, $newFileId = null, $params = [], $stream = false ) {
148 try {
149 $chatbot = null;
150 $customId = $params['customId'] ?? null;
151
152 // Custom Chatbot
153 if ( $customId ) {
154 $chatbot = get_transient( 'mwai_custom_chatbot_' . $customId );
155 }
156 // Registered Chatbot
157 if ( !$chatbot && $botId ) {
158 $chatbot = $this->core->get_chatbot( $botId );
159 }
160
161 if ( !$chatbot ) {
162 $this->core->log( "⚠️ No chatbot was found for this query.");
163 throw new Exception( 'Sorry, your query has been rejected.' );
164 }
165
166 $textInputMaxLength = $chatbot['textInputMaxLength'] ?? null;
167 if ( $textInputMaxLength && strlen( $newMessage ) > (int)$textInputMaxLength ) {
168 throw new Exception( 'Sorry, your query has been rejected.' );
169 }
170
171 // Create QueryText
172 $context = null;
173 $mode = $chatbot['mode'] ?? 'chat';
174
175 if ( $mode === 'images' ) {
176 $query = new Meow_MWAI_Query_Image( $newMessage );
177
178 // Handle Params
179 $newParams = [];
180 foreach ( $chatbot as $key => $value ) {
181 $newParams[$key] = $value;
182 }
183 foreach ( $params as $key => $value ) {
184 $newParams[$key] = $value;
185 }
186 $params = apply_filters( 'mwai_chatbot_params', $newParams );
187 $params['scope'] = empty( $params['scope'] ) ? 'chatbot' : $params['scope'];
188 $query->inject_params( $params );
189 }
190 else {
191 $query = $mode === 'assistant' ? new Meow_MWAI_Query_Assistant( $newMessage ) :
192 new Meow_MWAI_Query_Text( $newMessage, 1024 );
193 $streamCallback = null;
194
195 // Handle Params
196 $newParams = [];
197 foreach ( $chatbot as $key => $value ) {
198 $newParams[$key] = $value;
199 }
200 foreach ( $params as $key => $value ) {
201 $newParams[$key] = $value;
202 }
203 $params = apply_filters( 'mwai_chatbot_params', $newParams );
204 $params['scope'] = empty( $params['scope'] ) ? 'chatbot' : $params['scope'];
205 $query->inject_params( $params );
206
207 $storeId = null;
208 if ( $mode === 'assistant' ) {
209 $chatId = $params['chatId'] ?? null;
210 if ( !empty( $chatId ) ) {
211 $discussion = $this->core->discussions->get_discussion( $query->botId, $chatId );
212 if ( isset( $discussion['storeId'] ) ) {
213 $storeId = $discussion['storeId'];
214 $query->setStoreId( $storeId );
215 }
216 }
217 }
218
219 // Support for Uploaded Image
220 if ( !empty( $newFileId ) ) {
221
222 // Get extension and mime type
223 $isImage = $this->core->files->is_image( $newFileId );
224
225 if ( $mode === 'assistant' && !$isImage ) {
226 $url = $this->core->files->get_path( $newFileId );
227 $data = $this->core->files->get_data( $newFileId );
228 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $query->envId );
229 $filename = basename( $url );
230
231 // Upload the file
232 $file = $openai->upload_file( $filename, $data, 'assistants' );
233
234 // Create a store
235 if ( empty( $storeId ) ) {
236 $chatbotName = 'mwai_' . strtolower( !empty( $chatbot['name'] ) ? $chatbot['name'] : 'default' );
237 if ( !empty( $query->chatId ) ) {
238 $chatbotName .= "_" . $query->chatId;
239 }
240 $expiry = $this->core->get_option( 'image_expires' );
241 $metadata = [];
242 if ( !empty( $chatbot['assistantId'] ) ) {
243 $metadata['assistantId'] = $chatbot['assistantId'];
244 }
245 if ( !empty( $query->chatId ) ) {
246 $metadata['chatId'] = $query->chatId;
247 }
248 $storeId = $openai->create_vector_store( $chatbotName, $expiry, $metadata );
249 $query->setStoreId( $storeId );
250 }
251
252 // Add the file to the store
253 $storeFileId = $openai->add_vector_store_file( $storeId, $file['id'] );
254
255 // Update the local file with the OpenAI RefId, StoreId and StoreFileId
256 $openAiRefId = $file['id'];
257 $internalFileId = $this->core->files->get_id_from_refId( $newFileId );
258 $this->core->files->update_refId( $internalFileId, $openAiRefId );
259 $this->core->files->update_envId( $internalFileId, $query->envId );
260 $this->core->files->update_purpose( $internalFileId, 'assistant-in' );
261 $this->core->files->add_metadata( $internalFileId, 'assistant_storeId', $storeId );
262 $this->core->files->add_metadata( $internalFileId, 'assistant_storeFileId', $storeFileId );
263 $newFileId = $openAiRefId;
264 $scope = $params['fileSearch'];
265 if ( $scope === 'discussion' || $scope === 'user' || $scope === 'assistant' ) {
266 $id = $this->core->files->get_id_from_refId( $newFileId );
267 $this->core->files->add_metadata( $id, 'assistant_scope', $scope );
268 }
269 }
270 else {
271 $url = $this->core->files->get_url( $newFileId );
272 $mimeType = $this->core->files->get_mime_type( $newFileId );
273 $query->set_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'vision', $mimeType ) );
274 $fileId = $this->core->files->get_id_from_refId( $newFileId );
275 $this->core->files->update_envId( $fileId, $query->envId );
276 $this->core->files->update_purpose( $fileId, 'vision' );
277 $this->core->files->add_metadata( $fileId, 'query_envId', $query->envId );
278 $this->core->files->add_metadata( $fileId, 'query_session', $query->session );
279 }
280 }
281
282 // Takeover
283 $takeoverAnswer = apply_filters( 'mwai_chatbot_takeover', null, $query, $params );
284 if ( !empty( $takeoverAnswer ) ) {
285 return [
286 'reply' => $takeoverAnswer,
287 'images' => null,
288 'usage' => null
289 ];
290 }
291
292 // Moderation
293 $moderationEnabled = $this->core->get_option( 'module_moderation' ) &&
294 $this->core->get_option( 'shortcode_chat_moderation' );
295 if ( $moderationEnabled ) {
296 global $mwai;
297 $isFlagged = $mwai->moderationCheck( $query->get_message() );
298 if ( $isFlagged ) {
299 throw new Exception( 'Sorry, your message has been rejected by moderation.' );
300 }
301 }
302
303 // Awareness & Embeddings
304 $context = $this->core->retrieve_context( $params, $query );
305 if ( !empty( $context ) ) {
306 $query->set_context( $context['content'] );
307 }
308
309 // Function Aware
310 $query = apply_filters( 'mwai_chatbot_query', $query, $params );
311 }
312
313 // Process Query
314 if ( $stream ) {
315 $streamCallback = function( $reply ) {
316 $raw = $reply;
317 $this->stream_push( [ 'type' => 'live', 'data' => $raw ] );
318 if ( ob_get_level() > 0 ) {
319 ob_flush();
320 }
321 flush();
322 };
323 header( 'Cache-Control: no-cache' );
324 header( 'Content-Type: text/event-stream' );
325 // This is useful to disable buffering in nginx through headers.
326 header( 'X-Accel-Buffering: no' );
327 ob_implicit_flush( true );
328 ob_end_flush();
329 }
330
331 $reply = $this->core->run_query( $query, $streamCallback, true );
332 $rawText = $reply->result;
333 $extra = [];
334 if ( $context ) {
335 $extra = [ 'embeddings' => $context['embeddings'] ];
336 }
337 $rawText = apply_filters( 'mwai_chatbot_reply', $rawText, $query, $params, $extra );
338
339 $restRes = [
340 'reply' => $rawText,
341 'images' => $reply->get_type() === 'images' ? $reply->results : null,
342 'usage' => $reply->usage
343 ];
344
345 // Process Reply
346 if ( $stream ) {
347 $this->stream_push( [
348 'type' => 'end',
349 'data' => json_encode([
350 'success' => true,
351 'reply' => $restRes['reply'],
352 'images' => $restRes['images'],
353 'usage' => $restRes['usage']
354 ])
355 ] );
356 die();
357 }
358 else {
359 return $restRes;
360 }
361
362 }
363 catch ( Exception $e ) {
364 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
365 if ( $stream ) {
366 $this->stream_push( [ 'type' => 'error', 'data' => $message ] );
367 die();
368 }
369 else {
370 throw $e;
371 }
372 }
373 }
374
375 public function stream_push( $data ) {
376 $out = "data: " . json_encode( $data );
377 echo $out;
378 echo "\n\n";
379 if (ob_get_level() > 0) {
380 ob_end_flush();
381 }
382 flush();
383 }
384
385 public function inject_chat() {
386 $params = $this->core->get_chatbot( $this->siteWideChatId );
387 $clean_params = [];
388 if ( !empty( $params ) ) {
389 $clean_params['window'] = true;
390 $clean_params['id'] = $this->siteWideChatId;
391 echo $this->chat_shortcode( $clean_params );
392 }
393 return null;
394 }
395
396 public function build_front_params( $botId, $customId ) {
397 $frontSystem = [
398 'botId' => $customId ? null : $botId,
399 'customId' => $customId,
400 'userData' => $this->core->get_user_data(),
401 'sessionId' => $this->core->get_session_id(),
402 'restNonce' => $this->core->get_nonce(),
403 'contextId' => get_the_ID(),
404 'pluginUrl' => MWAI_URL,
405 'restUrl' => untrailingslashit( get_rest_url() ),
406 'debugMode' => $this->core->get_option( 'debug_mode' ),
407 'typewriter' => $this->core->get_option( 'chatbot_typewriter' ),
408 'speech_recognition' => $this->core->get_option( 'speech_recognition' ),
409 'speech_synthesis' => $this->core->get_option( 'speech_synthesis' ),
410 'stream' => $this->core->get_option( 'ai_streaming' ),
411 ];
412 return $frontSystem;
413 }
414
415 public function resolveBotInfo( &$atts )
416 {
417 $chatbot = null;
418 $botId = $atts['id'] ?? null;
419 $customId = $atts['custom_id'] ?? null;
420 if (!$botId && !$customId) {
421 $botId = "default";
422 }
423 if ( $botId ) {
424 $chatbot = $this->core->get_chatbot( $botId );
425 if (!$chatbot) {
426 $botId = $botId ?: 'N/A';
427 return [
428 'error' => "AI Engine: Chatbot '{$botId}' not found. If you meant to set an ID for your custom chatbot, please use 'custom_id' instead of 'id'.",
429 ];
430 }
431 }
432 $chatbot = $chatbot ?: $this->core->get_chatbot( 'default' );
433 if ( !empty( $customId ) ) {
434 $botId = null;
435 }
436 unset( $atts['id'] );
437 return [
438 'chatbot' => $chatbot,
439 'botId' => $botId,
440 'customId' => $customId,
441 ];
442 }
443
444 public function chat_shortcode( $atts ) {
445 $atts = empty( $atts ) ? [] : $atts;
446
447 // Let the user override the chatbot params
448 $atts = apply_filters( 'mwai_chatbot_params', $atts );
449
450 // Resolve the bot info
451 $resolvedBot = $this->resolveBotInfo( $atts, 'chatbot' );
452 if ( isset( $resolvedBot['error'] ) ) {
453 return $resolvedBot['error'];
454 }
455 $chatbot = $resolvedBot['chatbot'];
456 $botId = $resolvedBot['botId'];
457 $customId = $resolvedBot['customId'];
458
459 // Rename the keys of the atts into camelCase to match the internal params system.
460 $atts = array_map( function( $key, $value ) {
461 $key = str_replace( '_', ' ', $key );
462 $key = ucwords( $key );
463 $key = str_replace( ' ', '', $key );
464 $key = lcfirst( $key );
465 return [ $key => $value ];
466 }, array_keys( $atts ), $atts );
467 $atts = array_merge( ...$atts );
468
469 $frontParams = [];
470 foreach ( MWAI_CHATBOT_FRONT_PARAMS as $param ) {
471 if ( isset( $atts[$param] ) ) {
472 if ( $param === 'localMemory' ) {
473 $frontParams[$param] = $atts[$param] === 'true';
474 }
475 else {
476 $frontParams[$param] = $atts[$param];
477 }
478 }
479 else if ( isset( $chatbot[$param] ) ) {
480 $frontParams[$param] = $chatbot[$param];
481 }
482 }
483
484 // Server Params
485 // NOTE: We don't need the server params for the chatbot if there are no overrides, it means
486 // we are using the default or a specific chatbot.
487 $hasServerOverrides = count( array_intersect( array_keys( $atts ), MWAI_CHATBOT_SERVER_PARAMS ) ) > 0;
488 $serverParams = [];
489 if ( $hasServerOverrides ) {
490 foreach ( MWAI_CHATBOT_SERVER_PARAMS as $param ) {
491 if ( isset( $atts[$param] ) ) {
492 $serverParams[$param] = $atts[$param];
493 }
494 else {
495 $serverParams[$param] = $chatbot[$param] ?? null;
496 }
497 }
498 }
499
500 // Front Params
501 $frontSystem = $this->build_front_params( $botId, $customId );
502
503 // Clean Params
504 $frontParams = $this->clean_params( $frontParams );
505 $frontSystem = $this->clean_params( $frontSystem );
506 $serverParams = $this->clean_params( $serverParams );
507
508 // Server-side: Keep the System Params
509 if ( $hasServerOverrides ) {
510 if ( empty( $customId ) ) {
511 $customId = md5( json_encode( $serverParams ) );
512 $frontSystem['customId'] = $customId;
513 }
514 set_transient( 'mwai_custom_chatbot_' . $customId, $serverParams, 60 * 60 * 24 );
515 }
516
517 // Client-side: Prepare JSON for Front Params and System Params
518 $theme = isset( $frontParams['themeId'] ) ? $this->core->get_theme( $frontParams['themeId'] ) : null;
519 $jsonFrontParams = htmlspecialchars( json_encode( $frontParams ), ENT_QUOTES, 'UTF-8' );
520 $jsonFrontSystem = htmlspecialchars( json_encode( $frontSystem ), ENT_QUOTES, 'UTF-8' );
521 $jsonFrontTheme = htmlspecialchars( json_encode( $theme ), ENT_QUOTES, 'UTF-8' );
522 //$jsonAttributes = htmlspecialchars(json_encode($atts), ENT_QUOTES, 'UTF-8');
523
524 $this->enqueue_scripts();
525 return "<div class='mwai-chatbot-container' data-params='{$jsonFrontParams}' data-system='{$jsonFrontSystem}' data-theme='{$jsonFrontTheme}'></div>";
526 }
527
528 function chatbot_discussions( $atts ) {
529 $atts = empty($atts) ? [] : $atts;
530
531 // Resolve the bot info
532 $resolvedBot = $this->resolveBotInfo( $atts );
533 if ( isset( $resolvedBot['error'] ) ) {
534 return $resolvedBot['error'];
535 }
536 $chatbot = $resolvedBot['chatbot'];
537 $botId = $resolvedBot['botId'];
538 $customId = $resolvedBot['customId'];
539
540 // Rename the keys of the atts into camelCase to match the internal params system.
541 $atts = array_map( function( $key, $value ) {
542 $key = str_replace( '_', ' ', $key );
543 $key = ucwords( $key );
544 $key = str_replace( ' ', '', $key );
545 $key = lcfirst( $key );
546 return [ $key => $value ];
547 }, array_keys( $atts ), $atts );
548 $atts = array_merge( ...$atts );
549
550 // Front Params
551 $frontParams = [];
552 foreach ( MWAI_DISCUSSIONS_FRONT_PARAMS as $param ) {
553 if ( isset( $atts[$param] ) ) {
554 $frontParams[$param] = $atts[$param];
555 }
556 else if ( isset( $chatbot[$param] ) ) {
557 $frontParams[$param] = $chatbot[$param];
558 }
559 }
560
561 // Server Params
562 $serverParams = [];
563 foreach ( MWAI_DISCUSSIONS_SERVER_PARAMS as $param ) {
564 if ( isset( $atts[$param] ) ) {
565 $serverParams[$param] = $atts[$param];
566 }
567 }
568
569 // Front System
570 $frontSystem = $this->build_front_params( $botId, $customId );
571
572 // Clean Params
573 $frontParams = $this->clean_params( $frontParams );
574 $frontSystem = $this->clean_params( $frontSystem );
575 $serverParams = $this->clean_params( $serverParams );
576
577 $theme = isset( $frontParams['themeId'] ) ? $this->core->get_theme( $frontParams['themeId'] ) : null;
578 $jsonFrontParams = htmlspecialchars( json_encode( $frontParams ), ENT_QUOTES, 'UTF-8' );
579 $jsonFrontSystem = htmlspecialchars( json_encode( $frontSystem ), ENT_QUOTES, 'UTF-8' );
580 $jsonFrontTheme = htmlspecialchars( json_encode( $theme ), ENT_QUOTES, 'UTF-8' );
581
582 return "<div class='mwai-discussions-container' data-params='{$jsonFrontParams}' data-system='{$jsonFrontSystem}' data-theme='{$jsonFrontTheme}'></div>";
583 }
584
585 function clean_params( &$params ) {
586 foreach ( $params as $param => $value ) {
587 if ( $param === 'restNonce' ) {
588 continue;
589 }
590 if ( empty( $value ) || is_array( $value ) ) {
591 continue;
592 }
593 $lowerCaseValue = strtolower( $value );
594 if ( $lowerCaseValue === 'true' || $lowerCaseValue === 'false' || is_bool( $value ) ) {
595 $params[$param] = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
596 }
597 else if ( is_numeric( $value ) ) {
598 $params[$param] = filter_var( $value, FILTER_VALIDATE_FLOAT );
599 }
600 }
601 return $params;
602 }
603
604 }
605