$in chat_enabled, ai_problem (?string), simulated, paused, budget (?array{limit:int, available:int}), grounded, knowledge_enabled, * upgrade_pending, facts (ConnectionHealth::recent rows), last_success (unix time of the last answer on the connection, 0 = unknown), breaker_open, now * @return array{status:string, reasons:list, failures:list} */ public static function evaluate(array $in): array { if (!empty($in['simulated'])) { return ['status' => 'simulated', 'reasons' => [], 'failures' => []]; } $reasons = []; if (empty($in['chat_enabled'])) { $reasons[] = 'chat_off'; } if (is_string($in['ai_problem'] ?? null) && $in['ai_problem'] !== '') { $reasons[] = 'ai:' . $in['ai_problem']; } if (!empty($in['paused'])) { $reasons[] = 'paused'; } $budget = $in['budget'] ?? null; if (!is_array($budget) || (int) $budget['limit'] <= 0) { $reasons[] = 'no_budget'; } elseif ((int) $budget['available'] <= 0) { $reasons[] = 'budget_used_up'; } if (!empty($in['grounded']) && empty($in['knowledge_enabled'])) { $reasons[] = 'knowledge_off'; } if (!empty($in['upgrade_pending'])) { $reasons[] = 'upgrade_pending'; } if ($reasons !== []) { return ['status' => 'not_ready', 'reasons' => $reasons, 'failures' => []]; } $now = (int) $in['now']; $failures = []; foreach ((array) ($in['facts'] ?? []) as $fact) { if ($fact['category'] !== FailureCatalog::NO_ANSWER && $now - (int) $fact['last'] <= self::WINDOW && (int) $fact['last'] > (int) ($in['last_success'] ?? 0)) { $failures[] = (string) $fact['category']; } } if (!empty($in['breaker_open'])) { return ['status' => 'recent_failure', 'reasons' => ['breaker_open'], 'failures' => $failures]; } return ['status' => $failures === [] ? 'configured' : 'recent_failure', 'reasons' => [], 'failures' => $failures]; } public static function label(string $status): string { return match ($status) { 'not_ready' => __('Not ready according to local configuration', 'aiwend'), 'recent_failure' => __('Recent failure observed', 'aiwend'), 'simulated' => __('Simulated demo (no AI service is contacted)', 'aiwend'), default => __('Configured; no recent failure observed', 'aiwend'), }; } public static function reasonText(string $code): string { if (str_starts_with($code, 'ai:')) { $text = AiSetup::problemText(substr($code, 3)); return $text !== '' ? $text : __('The AI connection is not usable.', 'aiwend'); } return match ($code) { 'chat_off' => __('The Chat module is switched off.', 'aiwend'), 'paused' => __('New requests are paused (Usage page).', 'aiwend'), 'no_budget' => __('No site usage limit is set, so assistants do not answer.', 'aiwend'), 'budget_used_up' => __('The site usage limit is used up.', 'aiwend'), 'knowledge_off' => __('This assistant has knowledge attached, but the Knowledge module is off.', 'aiwend'), 'upgrade_pending' => __('A database update has not finished.', 'aiwend'), 'breaker_open' => __('The AI connection is paused for a few minutes after repeated requests ended with an unknown outcome.', 'aiwend'), default => __('Not ready.', 'aiwend'), }; } }