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