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