| @@ -329,9 +329,9 @@ | ||
| 329 | 329 | ], |
| 330 | 330 | 'provider' => [ |
| 331 | 331 | 'default' => 'all', |
| 332 | 332 | 'type' => 'string', |
| 333 | - 'enum' => ['all', 'openai', 'claude', 'gemini', 'openrouter'], | |
| 333 | + 'enum' => ['all', 'openai', 'claude', 'gemini', 'openrouter', 'openai_compatible'], | |
| 334 | 334 | 'sanitize_callback' => 'sanitize_key' |
| 335 | 335 | ], |
| 336 | 336 | 'user_id' => [ |
| 337 | 337 | 'default' => 0, |
| @@ -589,8 +589,12 @@ | ||
| 589 | 589 | 'openai' => 0, |
| 590 | 590 | 'claude' => 0, |
| 591 | 591 | 'gemini' => 0, |
| 592 | 592 | 'openrouter' => 0, |
| 593 | + // Costed only when the user told us what their endpoint charges; | |
| 594 | + // otherwise it stays 0 and the UI shows "—" rather than implying | |
| 595 | + // that a local model was free of charge or that we know the price. | |
| 596 | + 'openai_compatible' => 0, | |
| 593 | 597 | 'total' => 0, |
| 594 | 598 | 'by_provider' => [] |
| 595 | 599 | ]; |
| 596 | 600 | |
| @@ -617,9 +621,9 @@ | ||
| 617 | 621 | // get_detailed_usage_breakdown() so both tabs always agree. |
| 618 | 622 | $costs[$provider] += $this->calculate_record_cost($provider, $tokens, $model); |
| 619 | 623 | } |
| 620 | 624 | |
| 621 | - $costs['total'] = $costs['openai'] + $costs['claude'] + $costs['gemini'] + $costs['openrouter']; | |
| 625 | + $costs['total'] = $costs['openai'] + $costs['claude'] + $costs['gemini'] + $costs['openrouter'] + $costs['openai_compatible']; | |
| 622 | 626 | |
| 623 | 627 | // Report only providers that actually incurred cost. Emitting all four |
| 624 | 628 | // unconditionally meant a site with no AI usage rendered four ranked |
| 625 | 629 | // rows at "$0.0000 (0%)" — reading as "four providers were used and |
| @@ -625,9 +629,9 @@ | ||
| 625 | 629 | // rows at "$0.0000 (0%)" — reading as "four providers were used and |
| 626 | 630 | // each was free" — and made the panel's own "No provider cost data" |
| 627 | 631 | // empty state unreachable. |
| 628 | 632 | $costs['by_provider'] = []; |
| 629 | - foreach (['openai', 'claude', 'gemini', 'openrouter'] as $provider) { | |
| 633 | + foreach (['openai', 'claude', 'gemini', 'openrouter', 'openai_compatible'] as $provider) { | |
| 630 | 634 | if ($costs[$provider] <= 0) { |
| 631 | 635 | continue; |
| 632 | 636 | } |
| 633 | 637 | |
| @@ -1223,8 +1227,22 @@ | ||
| 1223 | 1227 | return self::OPENROUTER_PRICING[$model]; |
| 1224 | 1228 | } |
| 1225 | 1229 | return self::OPENROUTER_PRICING['openai/gpt-4o-mini'] ?? null; |
| 1226 | 1230 | |
| 1231 | + case 'openai_compatible': | |
| 1232 | + // There is no price table for someone else's server: it may be | |
| 1233 | + // a free local model, an Azure contract or a hosted open model. | |
| 1234 | + // The only honest number is the one the administrator entered, | |
| 1235 | + // as a flat per-1M-token rate applied to both directions. | |
| 1236 | + // | |
| 1237 | + // Read at report time, so changing the rate (or repointing the | |
| 1238 | + // provider at another server) re-costs past rows too. Accepted: | |
| 1239 | + // storing a price per row would mean a schema change for an | |
| 1240 | + // estimate the administrator typed in the first place. | |
| 1241 | + $price = (float) \ThinkRank\Core\Settings::instance()->get('openai_compatible_price_per_million', 0); | |
| 1242 | + | |
| 1243 | + return $price > 0 ? ['input' => $price, 'output' => $price] : null; | |
| 1244 | + | |
| 1227 | 1245 | default: |
| 1228 | 1246 | return null; |
| 1229 | 1247 | } |
| 1230 | 1248 | } |
| @@ -1244,8 +1262,11 @@ | ||
| 1244 | 1262 | case 'gemini': |
| 1245 | 1263 | return \ThinkRank\Core\Settings::DEFAULT_GEMINI_MODEL; |
| 1246 | 1264 | case 'openrouter': |
| 1247 | 1265 | return \ThinkRank\Core\Settings::DEFAULT_OPENROUTER_MODEL; |
| 1266 | + case 'openai_compatible': | |
| 1267 | + // Whatever the user pointed us at; there is no default. | |
| 1268 | + return (string) \ThinkRank\Core\Settings::instance()->get('openai_compatible_model', ''); | |
| 1248 | 1269 | default: |
| 1249 | 1270 | return 'unknown'; |
| 1250 | 1271 | } |
| 1251 | 1272 | } |