| @@ -964,31 +964,8 @@ | ||
| 964 | 964 | * @param string $prompt The prompt to send. |
| 965 | 965 | * @return array{ai_text:string,tokens:int} |
| 966 | 966 | */ |
| 967 | 967 | /** |
| 968 | - * Public plain-text completion against the configured provider. | |
| 969 | - * | |
| 970 | - * Thin gate over request_completion() for callers that need a free-form | |
| 971 | - * answer rather than a structured SEO artifact (e.g. the brand-visibility | |
| 972 | - * checker, which asks the model a user-style question and inspects the | |
| 973 | - * reply). Provider differences are already normalized inside. | |
| 974 | - * | |
| 975 | - * @since 1.27.0 | |
| 976 | - * | |
| 977 | - * @param string $prompt Prompt to send. | |
| 978 | - * @param int $max_tokens Output token ceiling. | |
| 979 | - * @return array{ai_text:string,tokens:int} | |
| 980 | - * @throws \Exception When no AI client is configured/available. | |
| 981 | - */ | |
| 982 | - public function answer_prompt(string $prompt, int $max_tokens = 1024, array $options = []): array { | |
| 983 | - if (!$this->client) { | |
| 984 | - throw new \Exception(wp_kses_post($this->get_client_unavailable_message())); | |
| 985 | - } | |
| 986 | - | |
| 987 | - return $this->request_completion($prompt, $max_tokens, $options); | |
| 988 | - } | |
| 989 | - | |
| 990 | - /** | |
| 991 | 968 | * Detect a provider-side refusal or content-policy block and fail with |
| 992 | 969 | * the real reason. Each provider signals these differently, and none of |
| 993 | 970 | * the signals set the content field the extraction chain looks for — left |
| 994 | 971 | * unchecked they read as an empty/unusable result with no explanation of |
| @@ -1070,10 +1047,10 @@ | ||
| 1070 | 1047 | $tokens = $response['usage']['total_tokens'] |
| 1071 | 1048 | ?? $response['usage']['output_tokens'] |
| 1072 | 1049 | ?? ($response['usageMetadata']['totalTokenCount'] ?? 0); |
| 1073 | 1050 | |
| 1074 | - // Diagnostics for callers that must explain an empty answer (e.g. the | |
| 1075 | - // brand-visibility probe): why generation stopped, and how much of the | |
| 1051 | + // Diagnostics for callers that must explain an empty answer: why | |
| 1052 | + // generation stopped, and how much of the | |
| 1076 | 1053 | // completion budget hidden reasoning consumed (OpenAI reasoning models). |
| 1077 | 1054 | // All three provider shapes are read — Gemini reports the stop reason |
| 1078 | 1055 | // per candidate, so without that arm the diagnostic was always blank |
| 1079 | 1056 | // for exactly the provider whose truncation it exists to explain. |
| @@ -1605,8 +1582,20 @@ | ||
| 1605 | 1582 | 'created_at' => current_time('mysql'), |
| 1606 | 1583 | ], |
| 1607 | 1584 | ['%d', '%s', '%d', '%s', '%s', '%s'] |
| 1608 | 1585 | ); |
| 1586 | + | |
| 1587 | + /** | |
| 1588 | + * Fires after an AI usage row is recorded. | |
| 1589 | + * | |
| 1590 | + * Analytics listens to drop its cached overview, so the Usages page | |
| 1591 | + * reflects this action immediately instead of after the 600s TTL. | |
| 1592 | + * | |
| 1593 | + * @since 2.2.1 | |
| 1594 | + * | |
| 1595 | + * @param int $user_id User the usage was recorded against. | |
| 1596 | + */ | |
| 1597 | + do_action('thinkrank_ai_usage_logged', $user_id); | |
| 1609 | 1598 | } |
| 1610 | 1599 | |
| 1611 | 1600 | /** |
| 1612 | 1601 | * Cleanup expired cache entries |