PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.0.6
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.0.6
3.5.9 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 / services / usage-stats.php
ai-engine / classes / services Last commit date
image.php 1 year ago message-builder.php 10 months ago model-environment.php 11 months ago response-id-manager.php 1 year ago session.php 11 months ago usage-stats.php 10 months ago
usage-stats.php
340 lines
1 <?php
2
3 class Meow_MWAI_Services_UsageStats {
4 private $core;
5 private $tiktoken_encoders = [];
6 private $encoder_provider = null;
7
8 public function __construct( $core ) {
9 $this->core = $core;
10 }
11
12 /**
13 * Get the cl100k_base tiktoken encoder
14 * Note: We always use cl100k_base regardless of model since it's the standard for all modern OpenAI models
15 * @return object|null The tiktoken encoder or null if not available
16 */
17 private function get_tiktoken_encoder() {
18 // Return cached encoder if available
19 if ( isset( $this->tiktoken_encoders['cl100k_base'] ) ) {
20 return $this->tiktoken_encoders['cl100k_base'];
21 }
22
23 try {
24 // Check if class exists
25 if ( !class_exists( 'Yethee\Tiktoken\EncoderProvider' ) ) {
26 error_log( '[AI Engine Tiktoken] EncoderProvider class not found. Check if composer autoload is working.' );
27 return null;
28 }
29
30 // Initialize encoder provider if needed
31 if ( $this->encoder_provider === null ) {
32 $this->encoder_provider = new \Yethee\Tiktoken\EncoderProvider();
33 }
34
35 // Get the cl100k_base encoder (standard for modern OpenAI models)
36 $encoder = $this->encoder_provider->get( 'cl100k_base' );
37 $this->tiktoken_encoders['cl100k_base'] = $encoder;
38
39 // Removed success log to reduce noise
40 return $encoder;
41 }
42 catch ( \Exception $e ) {
43 error_log( '[AI Engine Tiktoken] Failed to initialize encoder: ' . $e->getMessage() );
44 return null;
45 }
46 }
47
48 public function estimate_tokens( ...$args ) {
49 // Handle multiple argument formats for backward compatibility
50 $text = '';
51 $model = null;
52
53 // If first argument is an array, process messages
54 if ( !empty( $args[0] ) && is_array( $args[0] ) ) {
55 foreach ( $args[0] as $message ) {
56 $text .= isset( $message['content']['text'] ) ? $message['content']['text'] : '';
57 $text .= isset( $message['content'] ) && is_string( $message['content'] ) ? $message['content'] : '';
58 }
59 $model = $args[1] ?? null;
60 }
61 // Otherwise treat first argument as text
62 else {
63 $text = $args[0] ?? '';
64 $model = $args[1] ?? null;
65 }
66
67 // Convert to string if needed
68 if ( !is_string( $text ) ) {
69 // Handle arrays that weren't caught by the first condition
70 if ( is_array( $text ) ) {
71 $text = json_encode( $text );
72 }
73 // Handle objects
74 elseif ( is_object( $text ) ) {
75 $text = method_exists( $text, '__toString' ) ? (string) $text : json_encode( $text );
76 }
77 // Handle other types (int, float, bool, null)
78 else {
79 $text = (string) $text;
80 }
81 }
82
83 // Apply filters for customization
84 $text = apply_filters( 'mwai_estimate_tokens_text', $text, $model );
85 $tokens = apply_filters( 'mwai_estimate_tokens', null, $text, $model );
86 if ( $tokens !== null ) {
87 return $tokens;
88 }
89
90 // Try to use tiktoken for accurate counting (cl100k_base encoder)
91 $encoder = $this->get_tiktoken_encoder();
92 if ( $encoder ) {
93 try {
94 $encoded = $encoder->encode( $text );
95 $token_count = count( $encoded );
96
97 // Comparison logging removed - tiktoken is working correctly
98
99 return $token_count;
100 }
101 catch ( Exception $e ) {
102 error_log( '[AI Engine Tiktoken] Encoding failed, falling back to estimation: ' . $e->getMessage() );
103 }
104 }
105 else {
106 error_log( '[AI Engine Tiktoken] Encoder not available, using fallback' );
107 }
108
109 // Fallback to old estimation method
110 return $this->fallback_estimate_tokens( $text );
111 }
112
113 /**
114 * Fallback token estimation method (the original implementation)
115 */
116 private function fallback_estimate_tokens( $text ) {
117 $multiplier = 4;
118 $hasChineseChars = preg_match( '/[\x{4e00}-\x{9fa5}]/u', $text );
119 $hasJapaneseChars = preg_match( '/[\x{3040}-\x{309f}\x{30a0}-\x{30ff}]/u', $text );
120 $hasKoreanChars = preg_match( '/[\x{ac00}-\x{d7af}]/u', $text );
121 if ( $hasChineseChars || $hasJapaneseChars || $hasKoreanChars ) {
122 $multiplier = 2;
123 }
124 $tokens = (int) ( ( function_exists( 'mb_strlen' ) ? mb_strlen( $text ) : strlen( $text ) ) / $multiplier );
125 return $tokens;
126 }
127
128 public function record_tokens_usage( $model, $in_tokens, $out_tokens = 0, $returned_price = null ) {
129 if ( !is_numeric( $in_tokens ) ) {
130 $in_tokens = 0;
131 }
132 if ( !is_numeric( $out_tokens ) ) {
133 $out_tokens = 0;
134 }
135
136 // Record monthly usage
137 $usage = $this->core->get_option( 'ai_usage' );
138 $month = date( 'Y-m' );
139 if ( !isset( $usage[$month] ) ) {
140 $usage[$month] = [];
141 }
142 if ( !isset( $usage[$month][$model] ) ) {
143 $usage[$month][$model] = [
144 'prompt_tokens' => 0,
145 'completion_tokens' => 0,
146 'total_tokens' => 0,
147 'returned_price' => 0,
148 'queries' => 0
149 ];
150 }
151 // Ensure queries field exists for existing data
152 if ( !isset( $usage[$month][$model]['queries'] ) ) {
153 $usage[$month][$model]['queries'] = 0;
154 }
155 $usage[$month][$model]['prompt_tokens'] += $in_tokens;
156 $usage[$month][$model]['completion_tokens'] += $out_tokens;
157 $usage[$month][$model]['total_tokens'] += $in_tokens + $out_tokens;
158 $usage[$month][$model]['queries'] += 1;
159 if ( !empty( $returned_price ) ) {
160 // Ensure returned_price is numeric to avoid type errors
161 $price_value = is_array( $returned_price ) ?
162 ( isset( $returned_price['price'] ) ? $returned_price['price'] : 0 ) :
163 ( is_numeric( $returned_price ) ? $returned_price : 0 );
164 $usage[$month][$model]['returned_price'] += $price_value;
165 }
166
167 // Clean up old monthly data (keep only last 2 years)
168 $this->cleanup_old_monthly_data( $usage );
169 $this->core->update_option( 'ai_usage', $usage );
170
171 // Record daily usage
172 $daily_usage = $this->core->get_option( 'ai_usage_daily', [] );
173 $day = date( 'Y-m-d' );
174 if ( !isset( $daily_usage[$day] ) ) {
175 $daily_usage[$day] = [];
176 }
177 if ( !isset( $daily_usage[$day][$model] ) ) {
178 $daily_usage[$day][$model] = [
179 'prompt_tokens' => 0,
180 'completion_tokens' => 0,
181 'total_tokens' => 0,
182 'returned_price' => 0,
183 'queries' => 0
184 ];
185 }
186 // Ensure queries field exists for existing data
187 if ( !isset( $daily_usage[$day][$model]['queries'] ) ) {
188 $daily_usage[$day][$model]['queries'] = 0;
189 }
190 $daily_usage[$day][$model]['prompt_tokens'] += $in_tokens;
191 $daily_usage[$day][$model]['completion_tokens'] += $out_tokens;
192 $daily_usage[$day][$model]['total_tokens'] += $in_tokens + $out_tokens;
193 $daily_usage[$day][$model]['queries'] += 1;
194 if ( !empty( $returned_price ) ) {
195 $daily_usage[$day][$model]['returned_price'] += $returned_price;
196 }
197
198 // Clean up old daily data (keep only last 30 days)
199 $this->cleanup_old_daily_data( $daily_usage );
200 $this->core->update_option( 'ai_usage_daily', $daily_usage );
201
202 // Return the usage data for this specific request
203 return [
204 'prompt_tokens' => $in_tokens,
205 'completion_tokens' => $out_tokens,
206 'total_tokens' => $in_tokens + $out_tokens,
207 'price' => $returned_price,
208 'queries' => 1
209 ];
210 }
211
212 public function record_audio_usage( $model, $seconds ) {
213 // Record monthly usage
214 $usage = $this->core->get_option( 'ai_usage' );
215 $month = date( 'Y-m' );
216 if ( !isset( $usage[$month] ) ) {
217 $usage[$month] = [];
218 }
219 if ( !isset( $usage[$month][$model] ) ) {
220 $usage[$month][$model] = [ 'seconds' => 0, 'queries' => 0 ];
221 }
222 if ( !isset( $usage[$month][$model]['seconds'] ) ) {
223 $usage[$month][$model]['seconds'] = 0;
224 }
225 if ( !isset( $usage[$month][$model]['queries'] ) ) {
226 $usage[$month][$model]['queries'] = 0;
227 }
228 $usage[$month][$model]['seconds'] += $seconds;
229 $usage[$month][$model]['queries'] += 1;
230 $this->cleanup_old_monthly_data( $usage );
231 $this->core->update_option( 'ai_usage', $usage );
232
233 // Record daily usage
234 $daily_usage = $this->core->get_option( 'ai_usage_daily', [] );
235 $day = date( 'Y-m-d' );
236 if ( !isset( $daily_usage[$day] ) ) {
237 $daily_usage[$day] = [];
238 }
239 if ( !isset( $daily_usage[$day][$model] ) ) {
240 $daily_usage[$day][$model] = [ 'seconds' => 0, 'queries' => 0 ];
241 }
242 if ( !isset( $daily_usage[$day][$model]['seconds'] ) ) {
243 $daily_usage[$day][$model]['seconds'] = 0;
244 }
245 if ( !isset( $daily_usage[$day][$model]['queries'] ) ) {
246 $daily_usage[$day][$model]['queries'] = 0;
247 }
248 $daily_usage[$day][$model]['seconds'] += $seconds;
249 $daily_usage[$day][$model]['queries'] += 1;
250 $this->cleanup_old_daily_data( $daily_usage );
251 $this->core->update_option( 'ai_usage_daily', $daily_usage );
252
253 // Return the usage data for this specific request
254 return [
255 'seconds' => $seconds,
256 'queries' => 1
257 ];
258 }
259
260 public function record_images_usage( $model, $resolution, $images ) {
261 // Record monthly usage
262 $usage = $this->core->get_option( 'ai_usage' );
263 $month = date( 'Y-m' );
264 if ( !isset( $usage[$month] ) ) {
265 $usage[$month] = [];
266 }
267 if ( !isset( $usage[$month][$model] ) ) {
268 $usage[$month][$model] = [ 'resolution' => [], 'images' => 0, 'queries' => 0 ];
269 }
270 if ( !isset( $usage[$month][$model]['images'] ) ) {
271 $usage[$month][$model]['images'] = 0;
272 }
273 if ( !isset( $usage[$month][$model]['resolution'] ) ) {
274 $usage[$month][$model]['resolution'] = [];
275 }
276 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
277 $usage[$month][$model]['resolution'][$resolution] = 0;
278 }
279 if ( !isset( $usage[$month][$model]['queries'] ) ) {
280 $usage[$month][$model]['queries'] = 0;
281 }
282 $usage[$month][$model]['images'] += $images;
283 $usage[$month][$model]['resolution'][$resolution] += $images;
284 $usage[$month][$model]['queries'] += 1;
285 $this->cleanup_old_monthly_data( $usage );
286 $this->core->update_option( 'ai_usage', $usage );
287
288 // Record daily usage
289 $daily_usage = $this->core->get_option( 'ai_usage_daily', [] );
290 $day = date( 'Y-m-d' );
291 if ( !isset( $daily_usage[$day] ) ) {
292 $daily_usage[$day] = [];
293 }
294 if ( !isset( $daily_usage[$day][$model] ) ) {
295 $daily_usage[$day][$model] = [ 'resolution' => [], 'images' => 0, 'queries' => 0 ];
296 }
297 if ( !isset( $daily_usage[$day][$model]['images'] ) ) {
298 $daily_usage[$day][$model]['images'] = 0;
299 }
300 if ( !isset( $daily_usage[$day][$model]['resolution'] ) ) {
301 $daily_usage[$day][$model]['resolution'] = [];
302 }
303 if ( !isset( $daily_usage[$day][$model]['resolution'][$resolution] ) ) {
304 $daily_usage[$day][$model]['resolution'][$resolution] = 0;
305 }
306 if ( !isset( $daily_usage[$day][$model]['queries'] ) ) {
307 $daily_usage[$day][$model]['queries'] = 0;
308 }
309 $daily_usage[$day][$model]['images'] += $images;
310 $daily_usage[$day][$model]['resolution'][$resolution] += $images;
311 $daily_usage[$day][$model]['queries'] += 1;
312 $this->cleanup_old_daily_data( $daily_usage );
313 $this->core->update_option( 'ai_usage_daily', $daily_usage );
314
315 // Return the usage data for this specific request
316 return [
317 'images' => $images,
318 'queries' => 1
319 ];
320 }
321
322 private function cleanup_old_monthly_data( &$usage ) {
323 $two_years_ago = date( 'Y-m', strtotime( '-2 years' ) );
324 foreach ( $usage as $month => $data ) {
325 if ( $month < $two_years_ago ) {
326 unset( $usage[$month] );
327 }
328 }
329 }
330
331 private function cleanup_old_daily_data( &$usage ) {
332 $thirty_days_ago = date( 'Y-m-d', strtotime( '-30 days' ) );
333 foreach ( $usage as $day => $data ) {
334 if ( $day < $thirty_days_ago ) {
335 unset( $usage[$day] );
336 }
337 }
338 }
339 }
340