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