PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 1.9.97
AI Engine – The Chatbot, AI Framework & MCP for WordPress v1.9.97
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
engines 2 years ago modules 2 years ago queries 2 years ago admin.php 2 years ago api.php 2 years ago core.php 2 years ago init.php 3 years ago reply.php 2 years ago rest.php 2 years ago
core.php
792 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
14 class Meow_MWAI_Core
15 {
16 public $admin = null;
17 public $is_rest = false;
18 public $is_cli = false;
19 public $site_url = null;
20 public $ai = null;
21 public $files = null;
22 private $option_name = 'mwai_options';
23 private $themes_option_name = 'mwai_themes';
24 private $chatbots_option_name = 'mwai_chatbots';
25 private $nonce = null;
26 public $defaultChatbotParams = MWAI_CHATBOT_PARAMS;
27
28 // Cached
29 private $options = null;
30
31 public function __construct() {
32 $this->site_url = get_site_url();
33 $this->is_rest = MeowCommon_Helpers::is_rest();
34 $this->is_cli = defined( 'WP_CLI' );
35 $this->ai = new Meow_MWAI_Engines_Core( $this );
36 $this->files = new Meow_MWAI_Modules_Files( $this );
37
38 add_action( 'plugins_loaded', array( $this, 'init' ) );
39 add_action( 'wp_register_script', array( $this, 'register_scripts' ) );
40 add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) );
41 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
42 }
43
44 #region Init & Scripts
45 function init() {
46 global $mwai;
47 $chatbot_module = null;
48 $discussions_module = null;
49 new Meow_MWAI_Modules_Security( $this );
50 if ( $this->is_rest ) {
51 new Meow_MWAI_Rest( $this );
52 }
53 if ( is_admin() ) {
54 new Meow_MWAI_Admin( $this );
55 new Meow_MWAI_Modules_Assistants( $this );
56 }
57 if ( $this->get_option( 'shortcode_chat' ) ) {
58 $chatbot_module = new Meow_MWAI_Modules_Chatbot();
59 $discussions_module = new Meow_MWAI_Modules_Discussions();
60 // new Meow_MWAI_Modules_Chatbot_Legacy();
61 }
62
63 // Advanced core
64 if ( class_exists( 'MeowPro_MWAI_Core' ) ) {
65 new MeowPro_MWAI_Core( $this );
66 }
67
68 // Dynamic max tokens
69 if ( $this->get_option( 'dynamic_max_tokens' ) ) {
70 add_filter( 'mwai_estimate_tokens', array( $this, 'dynamic_max_tokens' ), 10, 2 );
71 }
72
73 $mwai = new Meow_MWAI_API( $chatbot_module, $discussions_module );
74 }
75
76 public function register_scripts() {
77 wp_register_script( 'mwai_highlight', MWAI_URL . 'vendor/highlightjs/highlight.min.js', [], '11.7', false );
78 }
79
80 public function enqueue_scripts() {
81 $this->register_scripts();
82 wp_enqueue_script( "mwai_highlight" );
83 }
84
85 #endregion
86
87 #region Roles & Capabilities
88
89 function can_access_settings() {
90 return apply_filters( 'mwai_allow_setup', current_user_can( 'manage_options' ) );
91 }
92
93 function can_access_features() {
94 $editor_or_admin = current_user_can( 'editor' ) || current_user_can( 'administrator' );
95 return apply_filters( 'mwai_allow_usage', $editor_or_admin );
96 }
97
98 function can_access_public_api( $feature, $extra ) {
99 $logged_in = is_user_logged_in();
100 return apply_filters( 'mwai_allow_public_api', $logged_in, $feature, $extra );
101 }
102
103 #endregion
104
105 #region Text-Related Helpers
106
107 // Clean the text perfectly, resolve shortcodes, etc, etc.
108 function cleanText( $rawText = "" ) {
109 $text = html_entity_decode( $rawText );
110 $text = wp_strip_all_tags( $text );
111 $text = preg_replace( '/[\r\n]+/', "\n", $text );
112 $text = preg_replace( '/\n+/', "\n", $text );
113 $text = preg_replace( '/\t+/', "\t", $text );
114 return $text . " ";
115 }
116
117 // Make sure there are no duplicate sentences, and keep the length under a maximum length.
118 function cleanSentences( $text, $maxTokens = null ) {
119 //$sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
120 $maxTokens = $maxTokens ? $maxTokens : $this->get_option( 'context_max_tokens', 1024 );
121 $sentences = preg_split('/(?<=[.?!。.!?])+/u', $text);
122 $hashes = array();
123 $uniqueSentences = array();
124 $length = 0;
125 foreach ( $sentences as $sentence ) {
126 $sentence = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $sentence );
127 $hash = md5( $sentence );
128 if ( !in_array( $hash, $hashes ) ) {
129 $tokensCount = apply_filters( 'mwai_estimate_tokens', 0, $sentence );
130 if ( $length + $tokensCount > $maxTokens ) {
131 continue;
132 }
133 $hashes[] = $hash;
134 $uniqueSentences[] = $sentence;
135 $length += $tokensCount;
136 }
137 }
138 $freshText = implode( " ", $uniqueSentences );
139 $freshText = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $freshText );
140 return $freshText;
141 }
142
143 function getCleanPostContent( $postId ) {
144 $post = get_post( $postId );
145 if ( !$post ) {
146 return false;
147 }
148 $text = apply_filters( 'mwai_pre_post_content', $post->post_content, $postId );
149 $pattern = '/\[mwai_.*?\]/';
150 $text = preg_replace( $pattern, '', $text );
151 if ( $this->get_option( 'resolve_shortcodes' ) ) {
152 $text = apply_filters( 'the_content', $text );
153 }
154 else {
155 $pattern = "/\[[^\]]+\]/";
156 $text = preg_replace( $pattern, '', $text );
157 $pattern = "/<!--\s*\/?wp:[^\>]+-->/";
158 $text = preg_replace( $pattern, '', $text );
159 }
160 $text = $this->cleanText( $text );
161 $text = $this->cleanSentences( $text );
162 $text = apply_filters( 'mwai_post_content', $text, $postId );
163 return $text;
164 }
165
166 function markdown_to_html( $content ) {
167 $Parsedown = new Parsedown();
168 $content = $Parsedown->text( $content );
169 return $content;
170 }
171
172 function get_post_language( $postId ) {
173 $locale = get_locale();
174 $code = strtolower( substr( $locale, 0, 2 ) );
175 $humanLanguage = strtr( $code, MWAI_ALL_LANGUAGES );
176 $lang = apply_filters( 'wpml_post_language_details', null, $postId );
177 if ( !empty( $lang ) ) {
178 $locale = $lang['locale'];
179 $humanLanguage = $lang['display_name'];
180 }
181 return strtolower( "$locale ($humanLanguage)" );
182 }
183 #endregion
184
185 #region Users/Sessions Helpers
186
187 function get_nonce() {
188 if ( !is_user_logged_in() ) {
189 return null;
190 }
191 if ( isset( $this->nonce ) ) {
192 return $this->nonce;
193 }
194 $this->nonce = wp_create_nonce( 'wp_rest' );
195 return $this->nonce;
196 }
197
198 function get_session_id() {
199 if ( isset( $_COOKIE['mwai_session_id'] ) ) {
200 return $_COOKIE['mwai_session_id'];
201 }
202 return "N/A";
203 }
204
205 // Get the UserID from the data, or from the current user
206 function get_user_id( $data = null ) {
207 if ( isset( $data ) && isset( $data['userId'] ) ) {
208 return (int)$data['userId'];
209 }
210 if ( is_user_logged_in() ) {
211 $current_user = wp_get_current_user();
212 if ( $current_user->ID > 0 ) {
213 return $current_user->ID;
214 }
215 }
216 return null;
217 }
218
219 function getUserData() {
220 $user = wp_get_current_user();
221 if ( empty( $user ) || empty( $user->ID ) ) {
222 return null;
223 }
224 $placeholders = array(
225 'FIRST_NAME' => get_user_meta( $user->ID, 'first_name', true ),
226 'LAST_NAME' => get_user_meta( $user->ID, 'last_name', true ),
227 'USER_LOGIN' => isset( $user ) && isset($user->data) && isset( $user->data->user_login ) ?
228 $user->data->user_login : null,
229 'DISPLAY_NAME' => isset( $user ) && isset( $user->data ) && isset( $user->data->display_name ) ?
230 $user->data->display_name : null,
231 'AVATAR_URL' => get_avatar_url( get_current_user_id() ),
232 );
233 return $placeholders;
234 }
235
236 function get_ip_address( $params = null ) {
237 $ip = '127.0.0.1';
238 $headers = [
239 'HTTP_TRUE_CLIENT_IP',
240 'HTTP_CF_CONNECTING_IP',
241 'HTTP_X_REAL_IP',
242 'HTTP_CLIENT_IP',
243 'HTTP_X_FORWARDED_FOR',
244 'HTTP_X_FORWARDED',
245 'HTTP_X_CLUSTER_CLIENT_IP',
246 'HTTP_FORWARDED_FOR',
247 'HTTP_FORWARDED',
248 'REMOTE_ADDR',
249 ];
250
251 if ( isset( $params ) && isset( $params[ 'ip' ] ) ) {
252 $ip = ( string )$params[ 'ip' ];
253 } else {
254 foreach ( $headers as $header ) {
255 if ( array_key_exists( $header, $_SERVER ) && !empty( $_SERVER[ $header ] && $_SERVER[ $header ] != '::1' ) ) {
256 $address_chain = explode( ',', wp_unslash( $_SERVER [ $header ] ) );
257 $ip = filter_var( trim( $address_chain[ 0 ] ), FILTER_VALIDATE_IP );
258 break;
259 }
260 }
261 }
262
263 return filter_var( apply_filters( 'mwai_get_ip_address', $ip ), FILTER_VALIDATE_IP );
264 }
265
266 #endregion
267
268 #region Other Helpers
269
270 function generateRandomId( $length = 8, $excludeIds = [] ) {
271 $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
272 $charactersLength = strlen( $characters );
273 $randomId = '';
274 for ( $i = 0; $i < $length; $i++ ) {
275 $randomId .= $characters[rand( 0, $charactersLength - 1 )];
276 }
277 if ( in_array( $randomId, $excludeIds ) ) {
278 return $this->generateRandomId( $length, $excludeIds );
279 }
280 return $randomId;
281 }
282
283 function isUrl( $url ) {
284 return strpos( $url, 'http' ) === 0 ? true : false;
285 }
286
287 function getPostTypes() {
288 $excluded = array( 'attachment', 'revision', 'nav_menu_item' );
289 $post_types = array();
290 $types = get_post_types( [], 'objects' );
291
292 // Let's get the Post Types that are enabled for Embeddings Sync
293 $embeddingsSettings = $this->get_option( 'embeddings' );
294 $syncPostTypes = isset( $embeddingsSettings['syncPostTypes'] ) ? $embeddingsSettings['syncPostTypes'] : [];
295
296 foreach ( $types as $type ) {
297 $forced = in_array( $type->name, $syncPostTypes );
298 // Should not be excluded.
299 if ( !$forced && in_array( $type->name, $excluded ) ) {
300 continue;
301 }
302 // Should be public.
303 if ( !$forced && !$type->public ) {
304 continue;
305 }
306 $post_types[] = array(
307 'name' => $type->labels->name,
308 'type' => $type->name,
309 );
310 }
311
312 // Let's get the Post Types that are enabled for Embeddings Sync
313 $embeddingsSettings = $this->get_option( 'embeddings' );
314 $syncPostTypes = isset( $embeddingsSettings['syncPostTypes'] ) ? $embeddingsSettings['syncPostTypes'] : [];
315
316 return $post_types;
317 }
318
319 function getCleanPost( $post ) {
320 if ( is_object( $post ) ) {
321 $post = (array)$post;
322 }
323 $language = $this->get_post_language( $post['ID'] );
324 $content = $this->getCleanPostContent( $post['ID'] );
325 $title = $post['post_title'];
326 $excerpt = $post['post_excerpt'];
327 $url = get_permalink( $post['ID'] );
328 $checksum = wp_hash( $content . $title . $url );
329 return [
330 'postId' => $post['ID'],
331 'title' => $title,
332 'content' => $content,
333 'excerpt' => $excerpt,
334 'url' => $url,
335 'language' => $language,
336 'checksum' => $checksum,
337 ];
338 }
339 #endregion
340
341 #region Usage & Costs
342
343 public function dynamic_max_tokens( $tokens, $text ) {
344 // Approximation (fast, no lib)
345 $asciiCount = 0;
346 $nonAsciiCount = 0;
347 for ( $i = 0; $i < mb_strlen( $text ); $i++ ) {
348 $char = mb_substr( $text, $i, 1 );
349 if ( ord( $char ) < 128 ) {
350 $asciiCount++;
351 }
352 else {
353 $nonAsciiCount++;
354 }
355 }
356 $asciiTokens = $asciiCount / 3.5;
357 $nonAsciiTokens = $nonAsciiCount * 2.5;
358 $tokens = $asciiTokens + $nonAsciiTokens;
359
360 // More exact (slower, and lib)
361 if ( PHP_VERSION_ID >= 70400 && function_exists( 'mb_convert_encoding' ) ) {
362 try {
363 $token_array = Encoder::encode( $text );
364 if ( !empty( $token_array ) ) {
365 $tokens = count( $token_array );
366 }
367 }
368 catch ( Exception $e ) {
369 error_log( $e->getMessage() );
370 }
371 }
372
373 $tokens = $tokens;
374 return (int)$tokens;
375 }
376
377 public function recordTokensUsage( $model, $prompt_tokens, $completion_tokens = 0 ) {
378 if ( !is_numeric( $prompt_tokens ) ) {
379 throw new Exception( 'Record usage: prompt_tokens is not a number.' );
380 }
381 if ( !is_numeric( $completion_tokens ) ) {
382 $completion_tokens = 0;
383 }
384 if ( !$model ) {
385 throw new Exception( 'Record usage: model is missing.' );
386 }
387 $usage = $this->get_option( 'openai_usage' );
388 $month = date( 'Y-m' );
389 if ( !isset( $usage[$month] ) ) {
390 $usage[$month] = array();
391 }
392 if ( !isset( $usage[$month][$model] ) ) {
393 $usage[$month][$model] = array(
394 'prompt_tokens' => 0,
395 'completion_tokens' => 0,
396 'total_tokens' => 0
397 );
398 }
399 $usage[$month][$model]['prompt_tokens'] += $prompt_tokens;
400 $usage[$month][$model]['completion_tokens'] += $completion_tokens;
401 $usage[$month][$model]['total_tokens'] += $prompt_tokens + $completion_tokens;
402 $this->update_option( 'openai_usage', $usage );
403 return [
404 'prompt_tokens' => $prompt_tokens,
405 'completion_tokens' => $completion_tokens,
406 'total_tokens' => $prompt_tokens + $completion_tokens
407 ];
408 }
409
410 public function record_audio_usage( $model, $seconds ) {
411 if ( !is_numeric( $seconds ) ) {
412 throw new Exception( 'Record usage: seconds is not a number.' );
413 }
414 if ( !$model ) {
415 throw new Exception( 'Record usage: model is missing.' );
416 }
417 $usage = $this->get_option( 'openai_usage' );
418 $month = date( 'Y-m' );
419 if ( !isset( $usage[$month] ) ) {
420 $usage[$month] = array();
421 }
422 if ( !isset( $usage[$month][$model] ) ) {
423 $usage[$month][$model] = array(
424 'seconds' => 0
425 );
426 }
427 $usage[$month][$model]['seconds'] += $seconds;
428 $this->update_option( 'openai_usage', $usage );
429 return [
430 'seconds' => $seconds
431 ];
432 }
433
434 public function record_images_usage( $model, $resolution, $images ) {
435 if ( !$model || !$resolution || !$images ) {
436 throw new Exception( 'Missing parameters for record_image_usage.' );
437 }
438 $usage = $this->get_option( 'openai_usage' );
439 $month = date( 'Y-m' );
440 if ( !isset( $usage[$month] ) ) {
441 $usage[$month] = array();
442 }
443 if ( !isset( $usage[$month][$model] ) ) {
444 $usage[$month][$model] = array(
445 'resolution' => array(),
446 'images' => 0
447 );
448 }
449 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
450 $usage[$month][$model]['resolution'][$resolution] = 0;
451 }
452 $usage[$month][$model]['resolution'][$resolution] += $images;
453 $usage[$month][$model]['images'] += $images;
454 $this->update_option( 'openai_usage', $usage );
455 return [
456 'resolution' => $resolution,
457 'images' => $images
458 ];
459 }
460
461 #endregion
462
463 #region Streaming
464 public function stream_push( $data ) {
465 $out = "data: " . json_encode( $data );
466 echo $out;
467 echo "\n\n";
468 if ( ob_get_level() > 0 ) {
469 ob_end_flush();
470 }
471 flush();
472 }
473 #endregion
474
475 #region Options
476 function getThemes() {
477 $themes = get_option( $this->themes_option_name, [] );
478 $themes = empty( $themes ) ? [] : $themes;
479
480 $internalThemes = [
481 'chatgpt' => [
482 'type' => 'internal', 'name' => 'ChatGPT', 'themeId' => 'chatgpt',
483 'settings' => [], 'style' => ""
484 ],
485 'messages' => [
486 'type' => 'internal', 'name' => 'Messages', 'themeId' => 'messages',
487 'settings' => [], 'style' => ""
488 ],
489 ];
490 $customThemes = [];
491 foreach ( $themes as $theme ) {
492 if ( isset( $internalThemes[$theme['themeId']] ) ) {
493 $internalThemes[$theme['themeId']] = $theme;
494 continue;
495 }
496 $customThemes[] = $theme;
497 }
498 return array_merge(array_values($internalThemes), $customThemes);
499 }
500
501 function updateThemes( $themes ) {
502 update_option( $this->themes_option_name, $themes );
503 return $themes;
504 }
505
506 function getChatbots() {
507 $chatbots = get_option( $this->chatbots_option_name, [] );
508 $hasChanges = false;
509 if ( empty( $chatbots ) ) {
510 $chatbots = [ array_merge( MWAI_CHATBOT_DEFAULT_PARAMS, ['name' => 'Default', 'botId' => 'default' ] ) ];
511 }
512 foreach ( $chatbots as &$chatbot ) {
513 foreach ( MWAI_CHATBOT_DEFAULT_PARAMS as $key => $value ) {
514 // Use default value if not set.
515 if ( !isset( $chatbot[$key] ) ) {
516 $chatbot[$key] = $value;
517 }
518 }
519 // TODO: After September 2023, let's remove this if statement.
520 if ( isset( $chatbot['chatId'] ) ) {
521 $chatbot['botId'] = $chatbot['chatId'];
522 unset( $chatbot['chatId'] );
523 $hasChanges = true;
524 }
525 // TODO: After September 2023, let's remove this if statement.
526 if ( empty( $chatbot['botId'] ) && $chatbot['name'] === 'default' ) {
527 $chatbot['botId'] = sanitize_title( $chatbot['name'] );
528 $hasChanges = true;
529 }
530 }
531 if ( $hasChanges ) {
532 update_option( $this->chatbots_option_name, $chatbots );
533 }
534 return $chatbots;
535 }
536
537 function getChatbot( $botId ) {
538 $chatbots = $this->getChatbots();
539 foreach ( $chatbots as $chatbot ) {
540 if ( $chatbot['botId'] === (string)$botId ) {
541 // Somehow, the default was set to "openai" when creating a new chatbot, but that overrided
542 // the default value in the Settings. It should be always empty here (except if we add this
543 // into the Settings of the chatbot).
544 $chatbot['service'] = null;
545 return $chatbot;
546 }
547 }
548 return null;
549 }
550
551 function getTheme( $themeId ) {
552 $themes = $this->getThemes();
553 foreach ( $themes as $theme ) {
554 if ( $theme['themeId'] === $themeId ) {
555 return $theme;
556 }
557 }
558 return null;
559 }
560
561 function updateChatbots( $chatbots ) {
562 $htmlFields = [ 'textCompliance', 'aiName', 'userName', 'startSentence' ];
563 $whiteSpacedFields = [ 'context' ];
564 foreach ( $chatbots as &$chatbot ) {
565 foreach ( $chatbot as $key => &$value ) {
566 if ( in_array( $key, $htmlFields ) ) {
567 $value = wp_kses_post( $value );
568 }
569 else if ( in_array( $key, $whiteSpacedFields ) ) {
570 $value = sanitize_textarea_field( $value );
571 }
572 else {
573 $value = sanitize_text_field( $value );
574 }
575 }
576 }
577
578 update_option( $this->chatbots_option_name, $chatbots );
579 return $chatbots;
580 }
581
582 function get_all_options( $force = false ) {
583 // We could cache options this way, but if we do, the apply_filters seems to be called too early.
584 // That causes issues with the mwai_languages filter.
585 // if ( !$force && !is_null( $this->options ) ) {
586 // return $this->options;
587 // }
588 $options = get_option( $this->option_name, [] );
589 $options = $this->sanitize_options( $options );
590 foreach ( MWAI_OPTIONS as $key => $value ) {
591 if ( !isset( $options[$key] ) ) {
592 $options[$key] = $value;
593 }
594 if ( $key === 'languages' ) {
595 // NOTE: If we decide to make a set of options for languages, we can keep it in the settings
596 $options[$key] = apply_filters( 'mwai_languages', MWAI_LANGUAGES );
597 }
598 }
599 $options['shortcode_chat_default_params'] = MWAI_CHATBOT_PARAMS;
600 $options['chatbot_defaults'] = MWAI_CHATBOT_DEFAULT_PARAMS;
601 $options['default_limits'] = MWAI_LIMITS;
602 $options['openai_models'] = Meow_MWAI_Engines_OpenAI::get_openai_models();
603 $options['fallback_model'] = MWAI_FALLBACK_MODEL;
604
605 $this->options = $options;
606 return $options;
607 }
608
609 // Sanitize options when we update the plugi or perform some updates
610 // if we change the structure of the options.
611 function sanitize_options( $options ) {
612 $needs_update = false;
613
614 // This upgrades namespace to multi-namespaces (June 2023)
615 // After January 2024, let's remove this.
616 if ( isset( $options['pinecone'] ) && isset( $options['pinecone']['namespace'] ) ) {
617 $options['pinecone']['namespaces'] = [ $options['pinecone']['namespace'] ];
618 unset( $options['pinecone']['namespace'] );
619 $needs_update = true;
620 }
621
622 // Support for Multi Vector DB Environments
623 // After June 2024, let's remove this.
624 if ( !isset( $options['embeddings_envs'] ) ) {
625 $options['embeddings_envs'] = [];
626 $default_id = $this->generateRandomId();
627 $pinecone = isset( $options['pinecone'] ) ? $options['pinecone'] : [];
628 $options['embeddings_envs'][] = [
629 'id' => $default_id,
630 'name' => 'Pinecone',
631 'type' => 'pinecone',
632 'apikey' => isset( $pinecone['apikey'] ) ? $pinecone['apikey'] : '',
633 'server' => isset( $pinecone['server'] ) ? $pinecone['server'] : 'gcp-starter',
634 'indexes' => isset( $pinecone['indexes'] ) ? $pinecone['indexes'] : [],
635 'namespaces' => isset( $pinecone['namespaces'] ) ? $pinecone['namespaces'] : [],
636 'index' => isset( $pinecone['index'] ) ? $pinecone['index'] : null,
637 ];
638 $options['embeddings_default_env'] = $default_id;
639 $needs_update = true;
640 }
641 if ( isset( $options['pinecone'] ) ) {
642 unset( $options['pinecone'] );
643 $needs_update = true;
644 }
645
646 // Support for Multi AI Environments
647 // After June 2024, let's remove this.
648 if ( !isset( $options['ai_envs'] ) ) {
649 $options['ai_envs'] = [];
650 $default_openai_id = $this->generateRandomId();
651 $default_azure_id = $this->generateRandomId();
652 $openai_service = isset( $options['openai_service'] ) ? $options['openai_service'] : 'openai';
653 $openai_apikey = isset( $options['openai_apikey'] ) ? $options['openai_apikey'] : '';
654 $azure_endpoint = isset( $options['openai_azure_endpoint'] ) ? $options['openai_azure_endpoint'] : '';
655
656 // OpenAI
657 // We create a default OpenAI environment if the API Key is set, or if the Azure Endpoint is not set.
658 if ( !empty( $openai_apikey ) || empty( $azure_endpoint ) ) {
659 $openai_finetunes = isset( $options['openai_finetunes'] ) ? $options['openai_finetunes'] : [];
660 $openai_finetunes_deleted = isset( $options['openai_finetunes_deleted'] ) ?
661 $options['openai_finetunes_deleted'] : [];
662 $openai_legacy_finetunes = isset( $options['openai_legacy_finetunes'] ) ?
663 $options['openai_legacy_finetunes'] : [];
664 $openai_legacy_finetunes_deleted = isset( $options['openai_legacy_finetunes_deleted'] ) ?
665 $options['openai_legacy_finetunes_deleted'] : [];
666 $options['ai_envs'][] = [
667 'id' => $default_openai_id,
668 'name' => 'OpenAI',
669 'type' => 'openai',
670 'apikey' => $openai_apikey,
671 'finetunes' => $openai_finetunes,
672 'finetunes_deleted' => $openai_finetunes_deleted,
673 'legacy_finetunes' => $openai_legacy_finetunes,
674 'legacy_finetunes_deleted' => $openai_legacy_finetunes_deleted
675 ];
676 }
677
678 // Azure
679 if ( !empty( $azure_endpoint ) ) {
680 $azure_apikey = isset( $options['openai_azure_apikey'] ) ? $options['openai_azure_apikey'] : '';
681 $azure_deployments = isset( $options['openai_azure_deployments'] ) ? $options['openai_azure_deployments'] : [];
682 $options['ai_envs'][] = [
683 'id' => $default_azure_id,
684 'name' => 'Azure',
685 'type' => 'azure',
686 'apikey' => $azure_apikey,
687 'endpoint' => $azure_endpoint,
688 'deployments' => $azure_deployments,
689 ];
690 }
691
692 $options['ai_default_env'] = $default_openai_id;
693 if ( $openai_service === 'azure' ) {
694 $options['ai_default_env'] = $default_azure_id;
695 }
696 $needs_update = true;
697 }
698
699 if ( !empty( $options['openai_apikey'] ) || !empty( $options['openai_azure_apikey'] ) ) {
700 unset( $options['openai_apikey'] );
701 unset( $options['openai_finetunes'] );
702 unset( $options['openai_finetunes_deleted'] );
703 unset( $options['openai_legacy_finetunes'] );
704 unset( $options['openai_legacy_finetunes_deleted'] );
705 unset( $options['openai_azure_apikey'] );
706 unset( $options['openai_azure_endpoint'] );
707 unset( $options['openai_azure_deployments'] );
708 unset( $options['openai_service'] );
709 $needs_update = true;
710 }
711
712 // The IDs for the embeddings environments are generated here.
713 // TODO: We should handle this more gracefully via an option in the Embeddings Settings.
714 $embeddings_default_exists = false;
715 if ( isset( $options['embeddings_envs'] ) ) {
716 foreach ( $options['embeddings_envs'] as &$env ) {
717 if ( !isset( $env['id'] ) ) {
718 $env['id'] = $this->generateRandomId();
719 $needs_update = true;
720 }
721 if ( $env['id'] === $options['embeddings_default_env'] ) {
722 $embeddings_default_exists = true;
723 }
724 }
725 }
726 if ( !$embeddings_default_exists ) {
727 $options['embeddings_default_env'] = $options['embeddings_envs'][0]['id'] ?? null;
728 $needs_update = true;
729 }
730
731 // The IDs for the AI environments are generated here.
732 $ai_default_exists = false;
733 if ( isset( $options['ai_envs'] ) ) {
734 foreach ( $options['ai_envs'] as &$env ) {
735 if ( !isset( $env['id'] ) ) {
736 $env['id'] = $this->generateRandomId();
737 $needs_update = true;
738 }
739 if ( $env['id'] === $options['ai_default_env'] ) {
740 $ai_default_exists = true;
741 }
742 }
743 }
744 if ( !$ai_default_exists ) {
745 $options['ai_default_env'] = $options['ai_envs'][0]['id'] ?? null;
746 $needs_update = true;
747 }
748
749 if ( $needs_update ) {
750 update_option( $this->option_name, $options, false );
751 }
752
753 return $options;
754 }
755
756 function update_options( $options ) {
757 if ( !update_option( $this->option_name, $options, false ) ) {
758 return false;
759 }
760 $options = $this->get_all_options( true );
761 return $options;
762 }
763
764 function update_option( $option, $value ) {
765 $options = $this->get_all_options( true );
766 $options[$option] = $value;
767 return $this->update_options( $options );
768 }
769
770 function get_option( $option, $default = null ) {
771 $options = $this->get_all_options();
772 return $options[$option] ?? $default;
773 }
774
775 function update_ai_env( $env_id, $option, $value ) {
776 $options = $this->get_all_options( true );
777 foreach ( $options['ai_envs'] as &$env ) {
778 if ( $env['id'] === $env_id ) {
779 $env[$option] = $value;
780 break;
781 }
782 }
783 return $this->update_options( $options );
784 }
785
786 function reset_options() {
787 return $this->update_options( MWAI_OPTIONS );
788 }
789 #endregion
790 }
791
792 ?>