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