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