PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.5.0
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.5.0
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 7 months ago message-builder.php 8 months ago model-environment.php 7 months ago response-id-manager.php 3 months ago session.php 11 months ago usage-stats.php 1 month ago
usage-stats.php
454 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 // The library defaults its vocab cache to {sys_temp}/tiktoken — a fixed shared
34 // path. On multi-tenant hosts where each site runs as a different uid, the first
35 // site to create the directory owns it and other sites get EACCES on write. Scope
36 // the cache by ABSPATH so every install gets its own writable directory.
37 $cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'tiktoken-' . substr( md5( ABSPATH ), 0, 12 );
38 $this->encoder_provider->setVocabCache( $cacheDir );
39 }
40
41 // Get the cl100k_base encoder (standard for modern OpenAI models)
42 $encoder = $this->encoder_provider->get( 'cl100k_base' );
43 $this->tiktoken_encoders['cl100k_base'] = $encoder;
44
45 // Removed success log to reduce noise
46 return $encoder;
47 }
48 catch ( \Exception $e ) {
49 error_log( '[AI Engine Tiktoken] Failed to initialize encoder: ' . $e->getMessage() );
50 return null;
51 }
52 }
53
54 public function estimate_tokens( $text = '', $model = null ) {
55 if ( !is_string( $text ) ) {
56 $text = is_array( $text ) || is_object( $text ) ? json_encode( $text ) : (string) $text;
57 }
58
59 // Apply filters for customization
60 $text = apply_filters( 'mwai_estimate_tokens_text', $text, $model );
61 $tokens = apply_filters( 'mwai_estimate_tokens', null, $text, $model );
62 if ( $tokens !== null ) {
63 return $tokens;
64 }
65
66 // Try to use tiktoken for accurate counting (cl100k_base encoder)
67 $encoder = $this->get_tiktoken_encoder();
68 if ( $encoder ) {
69 try {
70 $encoded = $encoder->encode( $text );
71 $token_count = count( $encoded );
72
73 // Comparison logging removed - tiktoken is working correctly
74
75 return $token_count;
76 }
77 catch ( Exception $e ) {
78 error_log( '[AI Engine Tiktoken] Encoding failed, falling back to estimation: ' . $e->getMessage() );
79 }
80 }
81 else {
82 error_log( '[AI Engine Tiktoken] Encoder not available, using fallback' );
83 }
84
85 // Fallback to old estimation method
86 return $this->fallback_estimate_tokens( $text );
87 }
88
89 /**
90 * Fallback token estimation method (the original implementation)
91 */
92 private function fallback_estimate_tokens( $text ) {
93 $multiplier = 4;
94 $hasChineseChars = preg_match( '/[\x{4e00}-\x{9fa5}]/u', $text );
95 $hasJapaneseChars = preg_match( '/[\x{3040}-\x{309f}\x{30a0}-\x{30ff}]/u', $text );
96 $hasKoreanChars = preg_match( '/[\x{ac00}-\x{d7af}]/u', $text );
97 if ( $hasChineseChars || $hasJapaneseChars || $hasKoreanChars ) {
98 $multiplier = 2;
99 }
100 $tokens = (int) ( ( function_exists( 'mb_strlen' ) ? mb_strlen( $text ) : strlen( $text ) ) / $multiplier );
101 return $tokens;
102 }
103
104 public function record_tokens_usage( $model, $in_tokens, $out_tokens = 0, $returned_price = null ) {
105 if ( !is_numeric( $in_tokens ) ) {
106 $in_tokens = 0;
107 }
108 if ( !is_numeric( $out_tokens ) ) {
109 $out_tokens = 0;
110 }
111
112 // Normalize returned_price - keep null when price is unavailable
113 $price_for_tracking = 0; // For monthly/daily accumulation
114 if ( !empty( $returned_price ) || $returned_price === 0 ) {
115 $returned_price = is_array( $returned_price ) ?
116 ( isset( $returned_price['price'] ) ? $returned_price['price'] : null ) :
117 ( is_numeric( $returned_price ) ? $returned_price : null );
118 $price_for_tracking = $returned_price ?? 0;
119 }
120 else {
121 $returned_price = null;
122 }
123
124 // Record monthly usage
125 $usage = $this->core->get_option( 'ai_usage' );
126 $month = date( 'Y-m' );
127 if ( !isset( $usage[$month] ) ) {
128 $usage[$month] = [];
129 }
130 if ( !isset( $usage[$month][$model] ) ) {
131 $usage[$month][$model] = [
132 'prompt_tokens' => 0,
133 'completion_tokens' => 0,
134 'total_tokens' => 0,
135 'returned_price' => 0,
136 'queries' => 0
137 ];
138 }
139 // Ensure all token fields exist for existing data
140 if ( !isset( $usage[$month][$model]['prompt_tokens'] ) ) {
141 $usage[$month][$model]['prompt_tokens'] = 0;
142 }
143 if ( !isset( $usage[$month][$model]['completion_tokens'] ) ) {
144 $usage[$month][$model]['completion_tokens'] = 0;
145 }
146 if ( !isset( $usage[$month][$model]['total_tokens'] ) ) {
147 $usage[$month][$model]['total_tokens'] = 0;
148 }
149 if ( !isset( $usage[$month][$model]['returned_price'] ) ) {
150 $usage[$month][$model]['returned_price'] = 0;
151 }
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 $usage[$month][$model]['returned_price'] += $price_for_tracking;
160
161 // Clean up old monthly data (keep only last 2 years)
162 $this->cleanup_old_monthly_data( $usage );
163 $this->core->update_option( 'ai_usage', $usage );
164
165 // Record daily usage
166 $daily_usage = $this->core->get_option( 'ai_usage_daily', [] );
167 $day = date( 'Y-m-d' );
168 if ( !isset( $daily_usage[$day] ) ) {
169 $daily_usage[$day] = [];
170 }
171 if ( !isset( $daily_usage[$day][$model] ) ) {
172 $daily_usage[$day][$model] = [
173 'prompt_tokens' => 0,
174 'completion_tokens' => 0,
175 'total_tokens' => 0,
176 'returned_price' => 0,
177 'queries' => 0
178 ];
179 }
180 // Ensure all token fields exist for existing data
181 if ( !isset( $daily_usage[$day][$model]['prompt_tokens'] ) ) {
182 $daily_usage[$day][$model]['prompt_tokens'] = 0;
183 }
184 if ( !isset( $daily_usage[$day][$model]['completion_tokens'] ) ) {
185 $daily_usage[$day][$model]['completion_tokens'] = 0;
186 }
187 if ( !isset( $daily_usage[$day][$model]['total_tokens'] ) ) {
188 $daily_usage[$day][$model]['total_tokens'] = 0;
189 }
190 if ( !isset( $daily_usage[$day][$model]['returned_price'] ) ) {
191 $daily_usage[$day][$model]['returned_price'] = 0;
192 }
193 if ( !isset( $daily_usage[$day][$model]['queries'] ) ) {
194 $daily_usage[$day][$model]['queries'] = 0;
195 }
196 $daily_usage[$day][$model]['prompt_tokens'] += $in_tokens;
197 $daily_usage[$day][$model]['completion_tokens'] += $out_tokens;
198 $daily_usage[$day][$model]['total_tokens'] += $in_tokens + $out_tokens;
199 $daily_usage[$day][$model]['queries'] += 1;
200 $daily_usage[$day][$model]['returned_price'] += $price_for_tracking;
201
202 // Clean up old daily data (keep only last 30 days)
203 $this->cleanup_old_daily_data( $daily_usage );
204 $this->core->update_option( 'ai_usage_daily', $daily_usage );
205
206 // Return the usage data for this specific request
207 return [
208 'prompt_tokens' => $in_tokens,
209 'completion_tokens' => $out_tokens,
210 'total_tokens' => $in_tokens + $out_tokens,
211 'price' => $returned_price,
212 'queries' => 1
213 ];
214 }
215
216 public function record_audio_usage( $model, $seconds ) {
217 // Record monthly usage
218 $usage = $this->core->get_option( 'ai_usage' );
219 $month = date( 'Y-m' );
220 if ( !isset( $usage[$month] ) ) {
221 $usage[$month] = [];
222 }
223 if ( !isset( $usage[$month][$model] ) ) {
224 $usage[$month][$model] = [ 'seconds' => 0, 'queries' => 0 ];
225 }
226 if ( !isset( $usage[$month][$model]['seconds'] ) ) {
227 $usage[$month][$model]['seconds'] = 0;
228 }
229 if ( !isset( $usage[$month][$model]['queries'] ) ) {
230 $usage[$month][$model]['queries'] = 0;
231 }
232 $usage[$month][$model]['seconds'] += $seconds;
233 $usage[$month][$model]['queries'] += 1;
234 $this->cleanup_old_monthly_data( $usage );
235 $this->core->update_option( 'ai_usage', $usage );
236
237 // Record daily usage
238 $daily_usage = $this->core->get_option( 'ai_usage_daily', [] );
239 $day = date( 'Y-m-d' );
240 if ( !isset( $daily_usage[$day] ) ) {
241 $daily_usage[$day] = [];
242 }
243 if ( !isset( $daily_usage[$day][$model] ) ) {
244 $daily_usage[$day][$model] = [ 'seconds' => 0, 'queries' => 0 ];
245 }
246 if ( !isset( $daily_usage[$day][$model]['seconds'] ) ) {
247 $daily_usage[$day][$model]['seconds'] = 0;
248 }
249 if ( !isset( $daily_usage[$day][$model]['queries'] ) ) {
250 $daily_usage[$day][$model]['queries'] = 0;
251 }
252 $daily_usage[$day][$model]['seconds'] += $seconds;
253 $daily_usage[$day][$model]['queries'] += 1;
254 $this->cleanup_old_daily_data( $daily_usage );
255 $this->core->update_option( 'ai_usage_daily', $daily_usage );
256
257 // Return the usage data for this specific request
258 return [
259 'seconds' => $seconds,
260 'queries' => 1
261 ];
262 }
263
264 public function record_images_usage( $model, $resolution, $images ) {
265 // Record monthly usage
266 $usage = $this->core->get_option( 'ai_usage' );
267 $month = date( 'Y-m' );
268 if ( !isset( $usage[$month] ) ) {
269 $usage[$month] = [];
270 }
271 if ( !isset( $usage[$month][$model] ) ) {
272 $usage[$month][$model] = [ 'resolution' => [], 'images' => 0, 'queries' => 0 ];
273 }
274 if ( !isset( $usage[$month][$model]['images'] ) ) {
275 $usage[$month][$model]['images'] = 0;
276 }
277 if ( !isset( $usage[$month][$model]['resolution'] ) ) {
278 $usage[$month][$model]['resolution'] = [];
279 }
280 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
281 $usage[$month][$model]['resolution'][$resolution] = 0;
282 }
283 if ( !isset( $usage[$month][$model]['queries'] ) ) {
284 $usage[$month][$model]['queries'] = 0;
285 }
286 $usage[$month][$model]['images'] += $images;
287 $usage[$month][$model]['resolution'][$resolution] += $images;
288 $usage[$month][$model]['queries'] += 1;
289 $this->cleanup_old_monthly_data( $usage );
290 $this->core->update_option( 'ai_usage', $usage );
291
292 // Record daily usage
293 $daily_usage = $this->core->get_option( 'ai_usage_daily', [] );
294 $day = date( 'Y-m-d' );
295 if ( !isset( $daily_usage[$day] ) ) {
296 $daily_usage[$day] = [];
297 }
298 if ( !isset( $daily_usage[$day][$model] ) ) {
299 $daily_usage[$day][$model] = [ 'resolution' => [], 'images' => 0, 'queries' => 0 ];
300 }
301 if ( !isset( $daily_usage[$day][$model]['images'] ) ) {
302 $daily_usage[$day][$model]['images'] = 0;
303 }
304 if ( !isset( $daily_usage[$day][$model]['resolution'] ) ) {
305 $daily_usage[$day][$model]['resolution'] = [];
306 }
307 if ( !isset( $daily_usage[$day][$model]['resolution'][$resolution] ) ) {
308 $daily_usage[$day][$model]['resolution'][$resolution] = 0;
309 }
310 if ( !isset( $daily_usage[$day][$model]['queries'] ) ) {
311 $daily_usage[$day][$model]['queries'] = 0;
312 }
313 $daily_usage[$day][$model]['images'] += $images;
314 $daily_usage[$day][$model]['resolution'][$resolution] += $images;
315 $daily_usage[$day][$model]['queries'] += 1;
316 $this->cleanup_old_daily_data( $daily_usage );
317 $this->core->update_option( 'ai_usage_daily', $daily_usage );
318
319 // Calculate price based on model and resolution
320 $price = 0;
321 $modelInfo = $this->get_model_info( $model );
322 if ( $modelInfo && isset( $modelInfo['resolutions'] ) ) {
323 foreach ( $modelInfo['resolutions'] as $res ) {
324 if ( $res['name'] === $resolution && isset( $res['price'] ) ) {
325 $price = $res['price'] * $images;
326 break;
327 }
328 }
329 }
330
331 // Return the usage data for this specific request
332 return [
333 'images' => $images,
334 'queries' => 1,
335 'price' => $price,
336 'accuracy' => $price > 0 ? 'price' : 'estimated' // 'price' = calculated from known pricing, not from API
337 ];
338 }
339
340 public function record_videos_usage( $model, $resolution, $seconds ) {
341 // Record monthly usage
342 $usage = $this->core->get_option( 'ai_usage' );
343 $month = date( 'Y-m' );
344 if ( !isset( $usage[$month] ) ) {
345 $usage[$month] = [];
346 }
347 if ( !isset( $usage[$month][$model] ) ) {
348 $usage[$month][$model] = [ 'resolution' => [], 'seconds' => 0, 'queries' => 0 ];
349 }
350 if ( !isset( $usage[$month][$model]['seconds'] ) ) {
351 $usage[$month][$model]['seconds'] = 0;
352 }
353 if ( !isset( $usage[$month][$model]['resolution'] ) ) {
354 $usage[$month][$model]['resolution'] = [];
355 }
356 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
357 $usage[$month][$model]['resolution'][$resolution] = 0;
358 }
359 if ( !isset( $usage[$month][$model]['queries'] ) ) {
360 $usage[$month][$model]['queries'] = 0;
361 }
362 $usage[$month][$model]['seconds'] += $seconds;
363 $usage[$month][$model]['resolution'][$resolution] += $seconds;
364 $usage[$month][$model]['queries'] += 1;
365 $this->cleanup_old_monthly_data( $usage );
366 $this->core->update_option( 'ai_usage', $usage );
367
368 // Record daily usage
369 $daily_usage = $this->core->get_option( 'ai_usage_daily', [] );
370 $day = date( 'Y-m-d' );
371 if ( !isset( $daily_usage[$day] ) ) {
372 $daily_usage[$day] = [];
373 }
374 if ( !isset( $daily_usage[$day][$model] ) ) {
375 $daily_usage[$day][$model] = [ 'resolution' => [], 'seconds' => 0, 'queries' => 0 ];
376 }
377 if ( !isset( $daily_usage[$day][$model]['seconds'] ) ) {
378 $daily_usage[$day][$model]['seconds'] = 0;
379 }
380 if ( !isset( $daily_usage[$day][$model]['resolution'] ) ) {
381 $daily_usage[$day][$model]['resolution'] = [];
382 }
383 if ( !isset( $daily_usage[$day][$model]['resolution'][$resolution] ) ) {
384 $daily_usage[$day][$model]['resolution'][$resolution] = 0;
385 }
386 if ( !isset( $daily_usage[$day][$model]['queries'] ) ) {
387 $daily_usage[$day][$model]['queries'] = 0;
388 }
389 $daily_usage[$day][$model]['seconds'] += $seconds;
390 $daily_usage[$day][$model]['resolution'][$resolution] += $seconds;
391 $daily_usage[$day][$model]['queries'] += 1;
392 $this->cleanup_old_daily_data( $daily_usage );
393 $this->core->update_option( 'ai_usage_daily', $daily_usage );
394
395 // Calculate price based on model, resolution, and seconds
396 $price = 0;
397 $modelInfo = $this->get_model_info( $model );
398 if ( $modelInfo && isset( $modelInfo['resolutions'] ) ) {
399 foreach ( $modelInfo['resolutions'] as $res ) {
400 if ( $res['name'] === $resolution && isset( $res['price'] ) ) {
401 // Price is per second for video models
402 $price = $res['price'] * $seconds;
403 break;
404 }
405 }
406 }
407
408 // Return the usage data for this specific request
409 return [
410 'seconds' => $seconds,
411 'queries' => 1,
412 'price' => $price,
413 'accuracy' => $price > 0 ? 'price' : 'estimated' // 'price' = calculated from known pricing, not from API
414 ];
415 }
416
417 private function get_model_info( $model ) {
418 $engines = $this->core->get_option( 'ai_engines' );
419 if ( !$engines || !is_array( $engines ) ) {
420 return null;
421 }
422
423 foreach ( $engines as $engine ) {
424 if ( isset( $engine['models'] ) && is_array( $engine['models'] ) ) {
425 foreach ( $engine['models'] as $modelInfo ) {
426 if ( isset( $modelInfo['model'] ) && $modelInfo['model'] === $model ) {
427 return $modelInfo;
428 }
429 }
430 }
431 }
432
433 return null;
434 }
435
436 private function cleanup_old_monthly_data( &$usage ) {
437 $two_years_ago = date( 'Y-m', strtotime( '-2 years' ) );
438 foreach ( $usage as $month => $data ) {
439 if ( $month < $two_years_ago ) {
440 unset( $usage[$month] );
441 }
442 }
443 }
444
445 private function cleanup_old_daily_data( &$usage ) {
446 $thirty_days_ago = date( 'Y-m-d', strtotime( '-30 days' ) );
447 foreach ( $usage as $day => $data ) {
448 if ( $day < $thirty_days_ago ) {
449 unset( $usage[$day] );
450 }
451 }
452 }
453 }
454