PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.3.4
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.3.4
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
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
572 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', 'iconAlt', 'iconPosition', 'fullscreen', 'copyButton'
9 ] );
10 define( 'MWAI_CHATBOT_SERVER_PARAMS', [ 'id', 'envId', 'scope', 'mode', 'contentAware', 'context',
11 'embeddingsEnvId', 'embeddingsIndex', 'embeddingsNamespace', 'assistantId', 'instructions',
12 'model', 'temperature', 'maxTokens', 'contextMaxLength', 'maxResults', 'apiKey', 'functions'
13 ] );
14
15 // Params for the discussions (front and server)
16
17 define( 'MWAI_DISCUSSIONS_FRONT_PARAMS', [ 'themeId', 'textNewChat' ] );
18 define( 'MWAI_DISCUSSIONS_SERVER_PARAMS', [ 'customId' ] );
19
20 class Meow_MWAI_Modules_Chatbot {
21 private $core = null;
22 private $namespace = 'mwai-ui/v1';
23 private $siteWideChatId = null;
24
25 public function __construct() {
26 global $mwai_core;
27 $this->core = $mwai_core;
28 add_shortcode( 'mwai_chatbot', array( $this, 'chat_shortcode' ) );
29 add_shortcode( 'mwai_chatbot_v2', array( $this, 'chat_shortcode' ) );
30 add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
31 $this->siteWideChatId = $this->core->get_option( 'botId' );
32 add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) );
33
34 if ( $this->core->get_option( 'shortcode_chat_discussions' ) ) {
35 add_shortcode( 'mwai_discussions', [ $this, 'shortcode_chat_discussions' ] );
36 }
37 }
38
39 public function register_scripts() {
40 $physical_file = trailingslashit( MWAI_PATH ) . 'app/chatbot.js';
41 $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : MWAI_VERSION;
42 wp_register_script( 'mwai_chatbot', trailingslashit( MWAI_URL ) . 'app/chatbot.js',
43 [ 'wp-element' ], $cache_buster, false );
44 if ( !empty( $this->siteWideChatId ) && $this->siteWideChatId !== 'none' ) {
45 $this->enqueue_scripts();
46 add_action( 'wp_footer', array( $this, 'inject_chat' ) );
47 }
48 }
49
50 public function enqueue_scripts() {
51 wp_enqueue_script( "mwai_chatbot" );
52 if ( $this->core->get_option( 'shortcode_chat_syntax_highlighting' ) ) {
53 wp_enqueue_script( "mwai_highlight" );
54 }
55 }
56
57 public function rest_api_init() {
58 register_rest_route( $this->namespace, '/chats/submit', array(
59 'methods' => 'POST',
60 'callback' => [ $this, 'rest_chat' ],
61 'permission_callback' => array( $this->core, 'check_rest_nonce' )
62 ) );
63 }
64
65 public function basics_security_check( $botId, $customId, $newMessage ) {
66 if ( empty( $newMessage ) ) {
67 error_log("AI Engine: The query was rejected - message was empty.");
68 return false;
69 }
70 if ( !$botId && !$customId ) {
71 error_log("AI Engine: The query was rejected - no botId nor id was specified.");
72 return false;
73 }
74
75 $length = strlen( $newMessage );
76 if ( $length < 1 || $length > ( 4096 * 16 ) ) {
77 error_log("AI Engine: The query was rejected - message was too short or too long.");
78 return false;
79 }
80 return true;
81 }
82
83 public function rest_chat( $request ) {
84 $params = $request->get_json_params();
85 $botId = $params['botId'] ?? null;
86 $customId = $params['customId'] ?? null;
87 $stream = $params['stream'] ?? false;
88 $newMessage = trim( $params['newMessage'] ?? '' );
89 $newFileId = $params['newFileId'] ?? null;
90
91 if ( !$this->basics_security_check( $botId, $customId, $newMessage )) {
92 return new WP_REST_Response( [
93 'success' => false,
94 'message' => apply_filters( 'mwai_ai_exception', 'Sorry, your query has been rejected.' )
95 ], 403 );
96 }
97
98 try {
99 $data = $this->chat_submit( $botId, $newMessage, $newFileId, $params, $stream );
100 return new WP_REST_Response( [
101 'success' => true,
102 'reply' => $data['reply'],
103 'images' => $data['images'],
104 'usage' => $data['usage']
105 ], 200 );
106 }
107 catch ( Exception $e ) {
108 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
109 return new WP_REST_Response( [
110 'success' => false,
111 'message' => $message
112 ], 500 );
113 }
114 }
115
116 public function chat_submit( $botId, $newMessage, $newFileId = null, $params = [], $stream = false ) {
117 try {
118 $chatbot = null;
119 $customId = $params['customId'] ?? null;
120
121 // Custom Chatbot
122 if ( $customId ) {
123 $chatbot = get_transient( 'mwai_custom_chatbot_' . $customId );
124 }
125 // Registered Chatbot
126 if ( !$chatbot && $botId ) {
127 $chatbot = $this->core->get_chatbot( $botId );
128 }
129
130 if ( !$chatbot ) {
131 error_log("AI Engine: No chatbot was found for this query.");
132 throw new Exception( 'Sorry, your query has been rejected.' );
133 }
134
135 $textInputMaxLength = $chatbot['textInputMaxLength'] ?? null;
136 if ( $textInputMaxLength && strlen( $newMessage ) > (int)$textInputMaxLength ) {
137 throw new Exception( 'Sorry, your query has been rejected.' );
138 }
139
140 // Create QueryText
141 $context = null;
142 $mode = $chatbot['mode'] ?? 'chat';
143
144 if ( $mode === 'images' ) {
145 $query = new Meow_MWAI_Query_Image( $newMessage );
146
147 // Handle Params
148 $newParams = [];
149 foreach ( $chatbot as $key => $value ) {
150 $newParams[$key] = $value;
151 }
152 foreach ( $params as $key => $value ) {
153 $newParams[$key] = $value;
154 }
155 $params = apply_filters( 'mwai_chatbot_params', $newParams );
156 $params['scope'] = empty( $params['scope'] ) ? 'chatbot' : $params['scope'];
157 $query->inject_params( $params );
158 }
159 else {
160 $query = $mode === 'assistant' ? new Meow_MWAI_Query_Assistant( $newMessage ) :
161 new Meow_MWAI_Query_Text( $newMessage, 1024 );
162 $streamCallback = null;
163
164 // Handle Params
165 $newParams = [];
166 foreach ( $chatbot as $key => $value ) {
167 $newParams[$key] = $value;
168 }
169 foreach ( $params as $key => $value ) {
170 $newParams[$key] = $value;
171 }
172 $params = apply_filters( 'mwai_chatbot_params', $newParams );
173 $params['scope'] = empty( $params['scope'] ) ? 'chatbot' : $params['scope'];
174 $query->inject_params( $params );
175
176 $storeId = null;
177 if ( $mode === 'assistant' ) {
178 $chatId = $params['chatId'] ?? null;
179 if ( !empty( $chatId ) ) {
180 $discussion = $this->core->discussions->get_discussion( $query->botId, $chatId );
181 if ( isset( $discussion['storeId'] ) ) {
182 $storeId = $discussion['storeId'];
183 $query->setStoreId( $storeId );
184 }
185 }
186 }
187
188 // Support for Uploaded Image
189 if ( !empty( $newFileId ) ) {
190
191 // Get extension and mime type
192 $isImage = $this->core->files->is_image( $newFileId );
193
194 if ( $mode === 'assistant' && !$isImage ) {
195 $url = $this->core->files->get_path( $newFileId );
196 $data = $this->core->files->get_data( $newFileId );
197 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $query->envId );
198 $filename = basename( $url );
199
200 // Upload the file
201 $file = $openai->upload_file( $filename, $data, 'assistants' );
202
203 // Create a store
204 if ( empty( $storeId ) ) {
205 $chatbotName = 'mwai_' . strtolower( !empty( $chatbot['name'] ) ? $chatbot['name'] : 'default' );
206 if ( !empty( $query->chatId ) ) {
207 $chatbotName .= "_" . $query->chatId;
208 }
209 $expiry = $this->core->get_option( 'image_expires' );
210 $metadata = [];
211 if ( !empty( $chatbot['assistantId'] ) ) {
212 $metadata['assistantId'] = $chatbot['assistantId'];
213 }
214 if ( !empty( $query->chatId ) ) {
215 $metadata['chatId'] = $query->chatId;
216 }
217 $storeId = $openai->create_vector_store( $chatbotName, $expiry, $metadata );
218 $query->setStoreId( $storeId );
219 }
220
221 // Add the file to the store
222 $storeFileId = $openai->add_vector_store_file( $storeId, $file['id'] );
223
224 // Update the local file with the OpenAI RefId, StoreId and StoreFileId
225 $openAiRefId = $file['id'];
226 $internalFileId = $this->core->files->get_id_from_refId( $newFileId );
227 $this->core->files->update_refId( $internalFileId, $openAiRefId );
228 $this->core->files->update_envId( $internalFileId, $query->envId );
229 $this->core->files->add_metadata( $internalFileId, 'assistant_storeId', $storeId );
230 $this->core->files->add_metadata( $internalFileId, 'assistant_storeFileId', $storeFileId );
231 $newFileId = $openAiRefId;
232 $scope = $params['fileSearch'];
233 if ( $scope === 'discussion' || $scope === 'user' || $scope === 'assistant' ) {
234 $id = $this->core->files->get_id_from_refId( $newFileId );
235 $this->core->files->add_metadata( $id, 'assistant_scope', $scope );
236 }
237 }
238 else {
239 $url = $this->core->files->get_url( $newFileId );
240 $mimeType = $this->core->files->get_mime_type( $newFileId );
241 $query->set_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'vision', $mimeType ) );
242 $fileId = $this->core->files->get_id_from_refId( $newFileId );
243 $this->core->files->update_envId( $fileId, $query->envId );
244 $this->core->files->add_metadata( $fileId, 'query_envId', $query->envId );
245 $this->core->files->add_metadata( $fileId, 'query_session', $query->session );
246 }
247 }
248
249 // Takeover
250 $takeoverAnswer = apply_filters( 'mwai_chatbot_takeover', null, $query, $params );
251 if ( !empty( $takeoverAnswer ) ) {
252 return [
253 'reply' => $takeoverAnswer,
254 'images' => null,
255 'usage' => null
256 ];
257 }
258
259 // Moderation
260 $moderationEnabled = $this->core->get_option( 'module_moderation' ) &&
261 $this->core->get_option( 'shortcode_chat_moderation' );
262 if ( $moderationEnabled ) {
263 global $mwai;
264 $isFlagged = $mwai->moderationCheck( $query->get_message() );
265 if ( $isFlagged ) {
266 throw new Exception( 'Sorry, your message has been rejected by moderation.' );
267 }
268 }
269
270 // Awareness & Embeddings
271 $context = $this->core->retrieve_context( $params, $query );
272 if ( !empty( $context ) ) {
273 $query->set_context( $context['content'] );
274 }
275
276 // Function Aware
277 $query = apply_filters( 'mwai_chatbot_query', $query, $params );
278 }
279
280 // Process Query
281 if ( $stream ) {
282 $streamCallback = function( $reply ) {
283 $raw = $reply;
284 $this->stream_push( [ 'type' => 'live', 'data' => $raw ] );
285 if ( ob_get_level() > 0 ) {
286 ob_flush();
287 }
288 flush();
289 };
290 header( 'Cache-Control: no-cache' );
291 header( 'Content-Type: text/event-stream' );
292 // This is useful to disable buffering in nginx through headers.
293 header( 'X-Accel-Buffering: no' );
294 ob_implicit_flush( true );
295 ob_end_flush();
296 }
297
298 $reply = $this->core->run_query( $query, $streamCallback, true );
299 $rawText = $reply->result;
300 $extra = [];
301 if ( $context ) {
302 $extra = [ 'embeddings' => $context['embeddings'] ];
303 }
304 $rawText = apply_filters( 'mwai_chatbot_reply', $rawText, $query, $params, $extra );
305
306 $restRes = [
307 'reply' => $rawText,
308 'images' => $reply->get_type() === 'images' ? $reply->results : null,
309 'usage' => $reply->usage
310 ];
311
312 // Process Reply
313 if ( $stream ) {
314 $this->stream_push( [
315 'type' => 'end',
316 'data' => json_encode([
317 'success' => true,
318 'reply' => $restRes['reply'],
319 'images' => $restRes['images'],
320 'usage' => $restRes['usage']
321 ])
322 ] );
323 die();
324 }
325 else {
326 return $restRes;
327 }
328
329 }
330 catch ( Exception $e ) {
331 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
332 if ( $stream ) {
333 $this->stream_push( [ 'type' => 'error', 'data' => $message ] );
334 die();
335 }
336 else {
337 throw $e;
338 }
339 }
340 }
341
342 public function stream_push( $data ) {
343 $out = "data: " . json_encode( $data );
344 echo $out;
345 echo "\n\n";
346 if (ob_get_level() > 0) {
347 ob_end_flush();
348 }
349 flush();
350 }
351
352 public function inject_chat() {
353 $params = $this->core->get_chatbot( $this->siteWideChatId );
354 $clean_params = [];
355 if ( !empty( $params ) ) {
356 $clean_params['window'] = true;
357 $clean_params['id'] = $this->siteWideChatId;
358 echo $this->chat_shortcode( $clean_params );
359 }
360 return null;
361 }
362
363 public function build_front_params( $botId, $customId ) {
364 $frontSystem = [
365 'botId' => $customId ? null : $botId,
366 'customId' => $customId,
367 'userData' => $this->core->get_user_data(),
368 'sessionId' => $this->core->get_session_id(),
369 'restNonce' => $this->core->get_nonce(),
370 'contextId' => get_the_ID(),
371 'pluginUrl' => MWAI_URL,
372 'restUrl' => untrailingslashit( get_rest_url() ),
373 'debugMode' => $this->core->get_option( 'debug_mode' ),
374 'typewriter' => $this->core->get_option( 'shortcode_chat_typewriter' ),
375 'speech_recognition' => $this->core->get_option( 'speech_recognition' ),
376 'speech_synthesis' => $this->core->get_option( 'speech_synthesis' ),
377 'stream' => $this->core->get_option( 'shortcode_chat_stream' ),
378 ];
379 return $frontSystem;
380 }
381
382 public function resolveBotInfo( &$atts )
383 {
384 $chatbot = null;
385 $botId = $atts['id'] ?? null;
386 $customId = $atts['custom_id'] ?? null;
387 if (!$botId && !$customId) {
388 $botId = "default";
389 }
390 if ( $botId ) {
391 $chatbot = $this->core->get_chatbot( $botId );
392 if (!$chatbot) {
393 $botId = $botId ?: 'N/A';
394 return [
395 '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'.",
396 ];
397 }
398 }
399 $chatbot = $chatbot ?: $this->core->get_chatbot( 'default' );
400 if ( !empty( $customId ) ) {
401 $botId = null;
402 }
403 unset( $atts['id'] );
404 return [
405 'chatbot' => $chatbot,
406 'botId' => $botId,
407 'customId' => $customId,
408 ];
409 }
410
411 public function chat_shortcode( $atts ) {
412 $atts = empty( $atts ) ? [] : $atts;
413
414 // Let the user override the chatbot params
415 $atts = apply_filters( 'mwai_chatbot_params', $atts );
416
417 // Resolve the bot info
418 $resolvedBot = $this->resolveBotInfo( $atts, 'chatbot' );
419 if ( isset( $resolvedBot['error'] ) ) {
420 return $resolvedBot['error'];
421 }
422 $chatbot = $resolvedBot['chatbot'];
423 $botId = $resolvedBot['botId'];
424 $customId = $resolvedBot['customId'];
425
426 // Rename the keys of the atts into camelCase to match the internal params system.
427 $atts = array_map( function( $key, $value ) {
428 $key = str_replace( '_', ' ', $key );
429 $key = ucwords( $key );
430 $key = str_replace( ' ', '', $key );
431 $key = lcfirst( $key );
432 return [ $key => $value ];
433 }, array_keys( $atts ), $atts );
434 $atts = array_merge( ...$atts );
435
436 $frontParams = [];
437 foreach ( MWAI_CHATBOT_FRONT_PARAMS as $param ) {
438 if ( isset( $atts[$param] ) ) {
439 if ( $param === 'localMemory' ) {
440 $frontParams[$param] = $atts[$param] === 'true';
441 }
442 else {
443 $frontParams[$param] = $atts[$param];
444 }
445 }
446 else if ( isset( $chatbot[$param] ) ) {
447 $frontParams[$param] = $chatbot[$param];
448 }
449 }
450
451 // Server Params
452 // NOTE: We don't need the server params for the chatbot if there are no overrides, it means
453 // we are using the default or a specific chatbot.
454 $hasServerOverrides = count( array_intersect( array_keys( $atts ), MWAI_CHATBOT_SERVER_PARAMS ) ) > 0;
455 $serverParams = [];
456 if ( $hasServerOverrides ) {
457 foreach ( MWAI_CHATBOT_SERVER_PARAMS as $param ) {
458 if ( isset( $atts[$param] ) ) {
459 $serverParams[$param] = $atts[$param];
460 }
461 else {
462 $serverParams[$param] = $chatbot[$param] ?? null;
463 }
464 }
465 }
466
467 // Front Params
468 $frontSystem = $this->build_front_params( $botId, $customId );
469
470 // Clean Params
471 $frontParams = $this->clean_params( $frontParams );
472 $frontSystem = $this->clean_params( $frontSystem );
473 $serverParams = $this->clean_params( $serverParams );
474
475 // Server-side: Keep the System Params
476 if ( $hasServerOverrides ) {
477 if ( empty( $customId ) ) {
478 $customId = md5( json_encode( $serverParams ) );
479 $frontSystem['customId'] = $customId;
480 }
481 set_transient( 'mwai_custom_chatbot_' . $customId, $serverParams, 60 * 60 * 24 );
482 }
483
484 // Client-side: Prepare JSON for Front Params and System Params
485 $theme = isset( $frontParams['themeId'] ) ? $this->core->get_theme( $frontParams['themeId'] ) : null;
486 $jsonFrontParams = htmlspecialchars( json_encode( $frontParams ), ENT_QUOTES, 'UTF-8' );
487 $jsonFrontSystem = htmlspecialchars( json_encode( $frontSystem ), ENT_QUOTES, 'UTF-8' );
488 $jsonFrontTheme = htmlspecialchars( json_encode( $theme ), ENT_QUOTES, 'UTF-8' );
489 //$jsonAttributes = htmlspecialchars(json_encode($atts), ENT_QUOTES, 'UTF-8');
490
491 $this->enqueue_scripts();
492 return "<div class='mwai-chatbot-container' data-params='{$jsonFrontParams}' data-system='{$jsonFrontSystem}' data-theme='{$jsonFrontTheme}'></div>";
493 }
494
495 function shortcode_chat_discussions( $atts ) {
496 $atts = empty($atts) ? [] : $atts;
497
498 // Resolve the bot info
499 $resolvedBot = $this->resolveBotInfo( $atts );
500 if ( isset( $resolvedBot['error'] ) ) {
501 return $resolvedBot['error'];
502 }
503 $chatbot = $resolvedBot['chatbot'];
504 $botId = $resolvedBot['botId'];
505 $customId = $resolvedBot['customId'];
506
507 // Rename the keys of the atts into camelCase to match the internal params system.
508 $atts = array_map( function( $key, $value ) {
509 $key = str_replace( '_', ' ', $key );
510 $key = ucwords( $key );
511 $key = str_replace( ' ', '', $key );
512 $key = lcfirst( $key );
513 return [ $key => $value ];
514 }, array_keys( $atts ), $atts );
515 $atts = array_merge( ...$atts );
516
517 // Front Params
518 $frontParams = [];
519 foreach ( MWAI_DISCUSSIONS_FRONT_PARAMS as $param ) {
520 if ( isset( $atts[$param] ) ) {
521 $frontParams[$param] = $atts[$param];
522 }
523 else if ( isset( $chatbot[$param] ) ) {
524 $frontParams[$param] = $chatbot[$param];
525 }
526 }
527
528 // Server Params
529 $serverParams = [];
530 foreach ( MWAI_DISCUSSIONS_SERVER_PARAMS as $param ) {
531 if ( isset( $atts[$param] ) ) {
532 $serverParams[$param] = $atts[$param];
533 }
534 }
535
536 // Front System
537 $frontSystem = $this->build_front_params( $botId, $customId );
538
539 // Clean Params
540 $frontParams = $this->clean_params( $frontParams );
541 $frontSystem = $this->clean_params( $frontSystem );
542 $serverParams = $this->clean_params( $serverParams );
543
544 $theme = isset( $frontParams['themeId'] ) ? $this->core->get_theme( $frontParams['themeId'] ) : null;
545 $jsonFrontParams = htmlspecialchars( json_encode( $frontParams ), ENT_QUOTES, 'UTF-8' );
546 $jsonFrontSystem = htmlspecialchars( json_encode( $frontSystem ), ENT_QUOTES, 'UTF-8' );
547 $jsonFrontTheme = htmlspecialchars( json_encode( $theme ), ENT_QUOTES, 'UTF-8' );
548
549 return "<div class='mwai-discussions-container' data-params='{$jsonFrontParams}' data-system='{$jsonFrontSystem}' data-theme='{$jsonFrontTheme}'></div>";
550 }
551
552 function clean_params( &$params ) {
553 foreach ( $params as $param => $value ) {
554 if ( $param === 'restNonce' ) {
555 continue;
556 }
557 if ( empty( $value ) || is_array( $value ) ) {
558 continue;
559 }
560 $lowerCaseValue = strtolower( $value );
561 if ( $lowerCaseValue === 'true' || $lowerCaseValue === 'false' || is_bool( $value ) ) {
562 $params[$param] = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
563 }
564 else if ( is_numeric( $value ) ) {
565 $params[$param] = filter_var( $value, FILTER_VALIDATE_FLOAT );
566 }
567 }
568 return $params;
569 }
570
571 }
572