'ChatGPT', 'perplexity' => 'Perplexity', 'gemini' => 'Gemini', 'claude' => 'Claude', 'copilot' => 'Copilot', 'meta-ai' => 'Meta AI', 'you' => 'You.com', 'poe' => 'Poe', 'grok' => 'Grok', 'mistral' => 'Mistral', 'deepseek' => 'DeepSeek', 'kimi' => 'Kimi', ]; public function key(): string { return 'ai_search'; } public function label(): string { return __('Traffic from AI assistants', 'thinkrank'); } public function default_enabled(): bool { return true; } public function requires_capability(): ?string { return null; } public function renders_own_heading(): bool { return true; } public function collect(array $context): array { $shared = $context['shared'] ?? []; $ai = $shared['ai'] ?? null; if (!is_array($ai) || !is_array($ai['current'] ?? null)) { return []; } $current = $ai['current']; $previous = is_array($ai['previous'] ?? null) ? $ai['previous'] : []; $prev_sessions = (int) ($previous['ai_sessions'] ?? 0); $prev_baseline = (int) ($previous['baseline'] ?? 0); return [ 'ai_sessions' => (int) ($current['ai_sessions'] ?? 0), 'baseline' => (int) ($current['baseline'] ?? 0), 'ai_share' => (float) ($current['ai_share'] ?? 0.0), 'prev_sessions' => $prev_baseline > 0 || $prev_sessions > 0 ? $prev_sessions : null, 'prev_share' => $prev_baseline > 0 ? round($prev_sessions / $prev_baseline * 100, 1) : null, 'platforms' => is_array($current['platforms'] ?? null) ? $current['platforms'] : [], 'crawler_hits' => (int) array_sum(array_map('intval', is_array($current['crawlers'] ?? null) ? $current['crawlers'] : [])), 'llms_txt' => !empty($current['llms_txt']), ]; } public function has_data(array $payload): bool { return (int) ($payload['ai_sessions'] ?? 0) > 0 || (int) ($payload['crawler_hits'] ?? 0) > 0; } public function render(array $payload): string { if (!$this->has_data($payload)) { return ''; } $sessions_change = ''; if ($payload['prev_sessions'] !== null) { $c = Email_Report_Html::pct_change((float) $payload['ai_sessions'], (float) $payload['prev_sessions']); $sessions_change = $c ? Email_Report_Html::change($c['text'], $c['direction'], $c['direction'] !== 'down') : ''; } $share_change = ''; if ($payload['prev_share'] !== null) { $pts = round((float) $payload['ai_share'] - (float) $payload['prev_share'], 1); if (abs($pts) >= 0.1) { $share_change = Email_Report_Html::change(number_format_i18n(abs($pts), 1), $pts > 0 ? 'up' : 'down', $pts > 0, __('pts', 'thinkrank')); } } $tiles = [ Email_Report_Html::stat([ 'label' => __('AI referral sessions', 'thinkrank'), 'value' => number_format_i18n((int) $payload['ai_sessions']), 'change' => $sessions_change, 'icon' => 'ai', ]), Email_Report_Html::stat([ 'label' => __('Share of all sessions', 'thinkrank'), 'value' => number_format_i18n((float) $payload['ai_share'], 1) . '%', 'change' => $share_change, 'icon' => 'share', ]), ]; $lines = []; $sources = $this->source_line((array) $payload['platforms'], (int) $payload['ai_sessions']); if ($sources !== '') { $lines[] = __('Top sources:', 'thinkrank') . ' ' . $sources; } if ((int) $payload['crawler_hits'] > 0) { $lines[] = sprintf( /* translators: %s: number of page fetches by AI crawlers. */ _n('AI crawlers fetched %s page this period.', 'AI crawlers fetched %s pages this period.', (int) $payload['crawler_hits'], 'thinkrank'), '' . esc_html(number_format_i18n((int) $payload['crawler_hits'])) . '' ); } $lines[] = !empty($payload['llms_txt']) ? __('Your llms.txt is published.', 'thinkrank') : __('Your llms.txt is not published yet, so AI crawlers get no guide to your site.', 'thinkrank'); $detail = '