PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 1.4.2
AI Engine – The Chatbot, AI Framework & MCP for WordPress v1.4.2
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / core.php
ai-engine / classes Last commit date
modules 3 years ago admin.php 3 years ago ai.php 3 years ago answer.php 3 years ago api.php 3 years ago core.php 3 years ago init.php 3 years ago openai.php 3 years ago query.php 3 years ago queryembed.php 3 years ago queryimage.php 3 years ago querytext.php 3 years ago querytranscribe.php 3 years ago rest.php 3 years ago security.php 3 years ago
core.php
466 lines
1 <?php
2
3 require_once( MWAI_PATH . '/vendor/autoload.php' );
4 require_once( MWAI_PATH . '/constants/init.php' );
5
6 use Rahul900day\Gpt3Encoder\Encoder;
7
8 define( 'MWAI_IMG_WAND', MWAI_URL . '/images/wand.png' );
9 define( 'MWAI_IMG_WAND_HTML', "<img style='height: 22px; margin-bottom: -5px; margin-right: 8px;'
10 src='" . MWAI_IMG_WAND . "' alt='AI Wand' />" );
11 define( 'MWAI_IMG_WAND_HTML_XS', "<img style='height: 16px; margin-bottom: -2px;'
12 src='" . MWAI_IMG_WAND . "' alt='AI Wand' />" );
13 class Meow_MWAI_Core
14 {
15 public $admin = null;
16 public $is_rest = false;
17 public $is_cli = false;
18 public $site_url = null;
19 public $ai = null;
20 private $option_name = 'mwai_options';
21 private $themes_option_name = 'mwai_themes';
22 private $chatbots_option_name = 'mwai_chatbots';
23 public $defaultChatbotParams = MWAI_CHATBOT_PARAMS;
24
25 public function __construct() {
26 $this->site_url = get_site_url();
27 $this->is_rest = MeowCommon_Helpers::is_rest();
28 $this->is_cli = defined( 'WP_CLI' ) && WP_CLI;
29 $this->ai = new Meow_MWAI_AI( $this );
30 add_action( 'plugins_loaded', array( $this, 'init' ) );
31 }
32
33 function init() {
34 global $mwai;
35 $mwai = new Meow_MWAI_API();
36 new Meow_MWAI_Security( $this );
37 if ( $this->is_rest ) {
38 new Meow_MWAI_Rest( $this );
39 }
40 if ( is_admin() ) {
41 new Meow_MWAI_Admin( $this );
42 new Meow_MWAI_Modules_Assistants( $this );
43 }
44 if ( $this->get_option( 'shortcode_chat' ) ) {
45 new Meow_MWAI_Modules_Chatbot();
46 new Meow_MWAI_Modules_Chatbot_Legacy();
47 new Meow_MWAI_Modules_Discussions();
48 }
49
50 // Advanced core
51 if ( class_exists( 'MeowPro_MWAI_Core' ) ) {
52 new MeowPro_MWAI_Core( $this );
53 }
54
55 // Dynamic max tokens
56 if ( $this->get_option( 'dynamic_max_tokens' ) ) {
57 add_filter( 'mwai_estimate_tokens', array( $this, 'dynamic_max_tokens' ), 10, 2 );
58 }
59 }
60
61 #region Roles & Capabilities
62
63 function can_access_settings() {
64 return apply_filters( 'mwai_allow_setup', current_user_can( 'manage_options' ) );
65 }
66
67 function can_access_features() {
68 $editor_or_admin = current_user_can( 'editor' ) || current_user_can( 'administrator' );
69 return apply_filters( 'mwai_allow_usage', $editor_or_admin );
70 }
71
72 #endregion
73
74 #region Text-Related Helpers
75
76 // Clean the text perfectly, resolve shortcodes, etc, etc.
77 function cleanText( $rawText = "" ) {
78 $text = html_entity_decode( $rawText );
79 $text = wp_strip_all_tags( $text );
80 $text = preg_replace( '/[\r\n]+/', "\n", $text );
81 return $text . " ";
82
83 // Before simplification:
84 // $text = strip_tags( $rawText );
85 // $text = strip_shortcodes( $text );
86 // $text = html_entity_decode( $text );
87 // $text = preg_replace( '/[\r\n]+/', "\n", $text );
88 // $sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
89 // foreach ( $sentences as $key => $sentence ) {
90 // $sentences[$key] = trim( $sentence );
91 // }
92 // $text = implode( " ", $sentences );
93 // $text = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $text );
94 // return $text . " ";
95 }
96
97 // Make sure there are no duplicate sentences, and keep the length under a maximum length.
98 function cleanSentences( $text, $maxTokens = 512 ) {
99 //$sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
100 $sentences = preg_split('/(?<=[.?!。.!?])+/u', $text);
101 $hashes = array();
102 $uniqueSentences = array();
103 $length = 0;
104 foreach ( $sentences as $sentence ) {
105 $sentence = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $sentence );
106 $hash = md5( $sentence );
107 if ( !in_array( $hash, $hashes ) ) {
108 $tokensCount = apply_filters( 'mwai_estimate_tokens', 0, $sentence );
109 if ( $length + $tokensCount > $maxTokens ) {
110 continue;
111 }
112 $hashes[] = $hash;
113 $uniqueSentences[] = $sentence;
114 $length += $tokensCount;
115 }
116 }
117 $freshText = implode( " ", $uniqueSentences );
118 $freshText = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $freshText );
119 return $freshText;
120 }
121
122 function getCleanPostContent( $postId ) {
123 $post = get_post( $postId );
124 if ( !$post ) {
125 return false;
126 }
127 $post->post_content = apply_filters( 'the_content', $post->post_content );
128 $text = $this->cleanText( $post->post_content );
129 $text = $this->cleanSentences( $text );
130 return $text;
131 }
132
133 function markdown_to_html( $content ) {
134 $Parsedown = new Parsedown();
135 $content = $Parsedown->text( $content );
136 return $content;
137 }
138
139 function get_post_language( $postId ) {
140 $locale = get_locale();
141 $code = strtolower( substr( $locale, 0, 2 ) );
142 $humanLanguage = strtr( $code, MWAI_ALL_LANGUAGES );
143 $lang = apply_filters( 'wpml_post_language_details', null, $postId );
144 if ( !empty( $lang ) ) {
145 $locale = $lang['locale'];
146 $humanLanguage = $lang['display_name'];
147 }
148 return strtolower( "$locale ($humanLanguage)" );
149 }
150 #endregion
151
152 #region Users/Sessions Helpers
153
154 function get_session_id() {
155 if ( isset( $_COOKIE['mwai_session_id'] ) ) {
156 return $_COOKIE['mwai_session_id'];
157 }
158 return "N/A";
159 }
160
161 // Get the UserID from the data, or from the current user
162 function get_user_id( $data = null ) {
163 if ( isset( $data ) && isset( $data['userId'] ) ) {
164 return (int)$data['userId'];
165 }
166 if ( is_user_logged_in() ) {
167 $current_user = wp_get_current_user();
168 if ( $current_user->ID > 0 ) {
169 return $current_user->ID;
170 }
171 }
172 return null;
173 }
174
175 function getUserData() {
176 $user = wp_get_current_user();
177 $placeholders = array(
178 'FIRST_NAME' => get_user_meta($user->ID, 'first_name', true),
179 'LAST_NAME' => get_user_meta($user->ID, 'last_name', true),
180 'USER_LOGIN' => $user->data->user_login,
181 'DISPLAY_NAME' => $user->data->display_name,
182 'AVATAR_URL' => get_avatar_url( get_current_user_id() ),
183 );
184 return $placeholders;
185 }
186
187 function get_ip_address( $data = null ) {
188 if ( isset( $data ) && isset( $data['ip'] ) ) {
189 $data['ip'] = (string)$data['ip'];
190 }
191 else {
192 if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
193 $data['ip'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
194 }
195 else if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
196 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_CLIENT_IP'] );
197 }
198 else if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
199 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_X_FORWARDED_FOR'] );
200 }
201 }
202 return $data['ip'];
203 }
204
205 #endregion
206
207 #region Other Helpers
208
209 function isUrl( $url ) {
210 return strpos( $url, 'http' ) === 0 ? true : false;
211 }
212
213 function getPostTypes() {
214 $excluded = array( 'attachment', 'revision', 'nav_menu_item' );
215 $post_types = array();
216 $types = get_post_types( array( 'public' => true ), 'objects' );
217 foreach ( $types as $type ) {
218 if ( in_array( $type->name, $excluded ) ) {
219 continue;
220 }
221 $post_types[] = array(
222 'name' => $type->labels->name,
223 'type' => $type->name,
224 );
225 }
226 return $post_types;
227 }
228
229 function getCleanPost( $post ) {
230 if ( is_object( $post ) ) {
231 $post = (array)$post;
232 }
233 $language = $this->get_post_language( $post['ID'] );
234 $content = apply_filters( 'mwai_pre_post_content', $post['post_content'], $post['ID'] );
235 $content = $this->cleanText( $content );
236 $content = apply_filters( 'mwai_post_content', $content, $post['ID'] );
237 $title = $post['post_title'];
238 $excerpt = $post['post_excerpt'];
239 $url = get_permalink( $post['ID'] );
240 $checksum = wp_hash( $content . $title . $url );
241 return [
242 'postId' => $post['ID'],
243 'title' => $title,
244 'content' => $content,
245 'excerpt' => $excerpt,
246 'url' => $url,
247 'language' => $language,
248 'checksum' => $checksum,
249 ];
250 }
251
252 #endregion
253
254 #region Usage & Costs
255
256 public function dynamic_max_tokens( $tokens, $text ) {
257 // Approximation (fast, no lib)
258 $asciiCount = 0;
259 $nonAsciiCount = 0;
260 for ( $i = 0; $i < mb_strlen( $text ); $i++ ) {
261 $char = mb_substr( $text, $i, 1 );
262 if ( ord( $char ) < 128 ) {
263 $asciiCount++;
264 }
265 else {
266 $nonAsciiCount++;
267 }
268 }
269 $asciiTokens = $asciiCount / 3.5;
270 $nonAsciiTokens = $nonAsciiCount * 2.5;
271 $tokens = $asciiTokens + $nonAsciiTokens;
272
273 // More exact (slower, and lib)
274 if ( PHP_VERSION_ID >= 70400 && function_exists( 'mb_convert_encoding' ) ) {
275 try {
276 $token_array = Encoder::encode( $text );
277 if ( !empty( $token_array ) ) {
278 $tokens = count( $token_array );
279 }
280 }
281 catch ( Exception $e ) {
282 error_log( $e->getMessage() );
283 }
284 }
285
286 $tokens = $tokens;
287 return (int)$tokens;
288 }
289
290 public function record_tokens_usage( $model, $prompt_tokens, $completion_tokens = 0 ) {
291 if ( !is_numeric( $prompt_tokens ) ) {
292 throw new Exception( 'Record usage: prompt_tokens is not a number.' );
293 }
294 if ( !is_numeric( $completion_tokens ) ) {
295 $completion_tokens = 0;
296 }
297 if ( !$model ) {
298 throw new Exception( 'Record usage: model is missing.' );
299 }
300 $usage = $this->get_option( 'openai_usage' );
301 $month = date( 'Y-m' );
302 if ( !isset( $usage[$month] ) ) {
303 $usage[$month] = array();
304 }
305 if ( !isset( $usage[$month][$model] ) ) {
306 $usage[$month][$model] = array(
307 'prompt_tokens' => 0,
308 'completion_tokens' => 0,
309 'total_tokens' => 0
310 );
311 }
312 $usage[$month][$model]['prompt_tokens'] += $prompt_tokens;
313 $usage[$month][$model]['completion_tokens'] += $completion_tokens;
314 $usage[$month][$model]['total_tokens'] += $prompt_tokens + $completion_tokens;
315 $this->update_option( 'openai_usage', $usage );
316 return [
317 'prompt_tokens' => $prompt_tokens,
318 'completion_tokens' => $completion_tokens,
319 'total_tokens' => $prompt_tokens + $completion_tokens
320 ];
321 }
322
323 public function record_images_usage( $model, $resolution, $images ) {
324 if ( !$model || !$resolution || !$images ) {
325 throw new Exception( 'Missing parameters for record_image_usage.' );
326 }
327 $usage = $this->get_option( 'openai_usage' );
328 $month = date( 'Y-m' );
329 if ( !isset( $usage[$month] ) ) {
330 $usage[$month] = array();
331 }
332 if ( !isset( $usage[$month][$model] ) ) {
333 $usage[$month][$model] = array(
334 'resolution' => array(),
335 'images' => 0
336 );
337 }
338 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
339 $usage[$month][$model]['resolution'][$resolution] = 0;
340 }
341 $usage[$month][$model]['resolution'][$resolution] += $images;
342 $usage[$month][$model]['images'] += $images;
343 $this->update_option( 'openai_usage', $usage );
344 return [
345 'resolution' => $resolution,
346 'images' => $images
347 ];
348 }
349
350 #endregion
351
352 #region Options
353 function getThemes() {
354 $themes = get_option( $this->themes_option_name, [] );
355 if ( empty( $themes ) ) {
356 $themes = [ [
357 'type' => 'internal',
358 'name' => 'ChatGPT',
359 'themeId' => 'chatgpt',
360 'settings' => [],
361 'style' => ""
362 ] ];
363 }
364 return $themes;
365 }
366
367 function updateThemes( $themes ) {
368 update_option( $this->themes_option_name, $themes );
369 return $themes;
370 }
371
372 function getChatbots() {
373 $chatbots = get_option( $this->chatbots_option_name, [] );
374 if ( empty( $chatbots ) ) {
375 $chatbots = [ array_merge( MWAI_CHATBOT_DEFAULT_PARAMS, ['name' => 'Default', 'chatId' => 'default' ] ) ];
376 }
377 foreach ( $chatbots as $chatbot ) {
378 foreach ( MWAI_CHATBOT_DEFAULT_PARAMS as $key => $value ) {
379 if ( !isset( $chatbot[$key] ) ) {
380 $chatbot[$key] = $value;
381 }
382 }
383 }
384 return $chatbots;
385 }
386
387 function getChatbot( $chatId ) {
388 $chatbots = $this->getChatbots();
389 foreach ( $chatbots as $chatbot ) {
390 if ( $chatbot['chatId'] === $chatId ) {
391 return $chatbot;
392 }
393 }
394 return null;
395 }
396
397 function getTheme( $themeId ) {
398 $themes = $this->getThemes();
399 foreach ( $themes as $theme ) {
400 if ( $theme['themeId'] === $themeId ) {
401 return $theme;
402 }
403 }
404 return null;
405 }
406
407 function updateChatbots( $chatbots ) {
408 update_option( $this->chatbots_option_name, $chatbots );
409 return $chatbots;
410 }
411
412 function get_all_options() {
413 $options = get_option( $this->option_name, null );
414 foreach ( MWAI_OPTIONS as $key => $value ) {
415 if ( !isset( $options[$key] ) ) {
416 $options[$key] = $value;
417 }
418 if ( $key === 'languages' ) {
419 // TODO: If we decide to make a set of options for languages, we can keep it in the settings
420 $options[$key] = MWAI_LANGUAGES;
421 $options[$key] = apply_filters( 'mwai_languages', $options[$key] );
422 }
423 }
424 $options['shortcode_chat_default_params'] = MWAI_CHATBOT_PARAMS;
425 $options['chatbot_defaults'] = MWAI_CHATBOT_DEFAULT_PARAMS;
426 $options['default_limits'] = MWAI_LIMITS;
427 $options['openai_models'] = MWAI_OPENAI_MODELS;
428 return $options;
429 }
430
431 // Validate and keep the options clean and logical.
432 function sanitize_options() {
433 $options = $this->get_all_options();
434 $needs_update = false;
435
436 // We can sanitize our future options here, let's always remember it.
437 // Now, it is empty...
438
439 if ( $needs_update ) {
440 update_option( $this->option_name, $options, false );
441 }
442 return $options;
443 }
444
445 function update_options( $options ) {
446 if ( !update_option( $this->option_name, $options, false ) ) {
447 return false;
448 }
449 $options = $this->sanitize_options();
450 return $options;
451 }
452
453 function update_option( $option, $value ) {
454 $options = $this->get_all_options();
455 $options[$option] = $value;
456 return $this->update_options( $options );
457 }
458
459 function get_option( $option, $default = null ) {
460 $options = $this->get_all_options();
461 return $options[$option] ?? $default;
462 }
463 #endregion
464 }
465
466 ?>