@php /** @var \App\Models\KeywordReply|null $row */ $row = $row ?? null; $analytics = $analytics ?? [ 'recent' => collect(), 'topUsers' => collect(), 'variantStats' => collect(), 'hourBuckets' => array_fill(0, 24, 0), 'dayBuckets' => collect(), 'fired7d' => 0, 'fired30d' => 0, 'uniqueUsers' => 0, 'funnel' => [], 'latencyAvgMs' => null, 'latencyP95Ms' => null, ]; // Mask phone numbers for display — first 4 + last 3, hide middle digits // so the operator can identify the contact without leaking full PII // into screenshots/exports. $maskPhone = function (?string $p): string { $p = (string) $p; if (mb_strlen($p) < 8) { return $p ?: '—'; } return mb_substr($p, 0, 4) . '••••' . mb_substr($p, -3); }; $phoneInitials = function (?string $p): string { $p = (string) $p; return mb_strtoupper(mb_substr(preg_replace('/[^0-9]/', '', $p), -2)); }; $keyword = $row?->keyword ?: '—'; $tag = mb_strtoupper(mb_substr($keyword, 0, 2)); $matchSub = $row ? match ($row->matching_method) { 'fuzzy' => 'fuzzy / ' . ($row->fuzzy_similarity ?: 80) . '%', 'contains' => 'contains', default => 'exact', } : '—'; $deviceLabel = optional($row?->device)->phone_number ?: '—'; $statusActive = $row && $row->status; $totalTriggers = (int) ($row?->trigger_count ?? 0); $lastFired = $row?->last_triggered_at; $contents = $row?->contents ?? collect(); $typePill = function ($r) { if (!$r) { return ['cls' => 'bg-paper-100 text-ink-500', 'label' => '—']; } $kind = $r->reply_type === 'flow' ? 'flow' : ($r->message_type ?: 'text'); return match ($kind) { 'flow' => [ 'cls' => 'bg-[#D9E5F2] text-[#13478A]', 'label' => 'Flow / ' . (optional($r->flow)->flow_name ?: 'Flow'), ], 'image' => ['cls' => 'bg-wa-deep/10 text-wa-deep', 'label' => 'Custom · Image'], 'video' => ['cls' => 'bg-wa-deep/10 text-wa-deep', 'label' => 'Custom · Video'], 'document' => ['cls' => 'bg-accent-amber/20 text-[#7B5A14]', 'label' => 'Custom · Document'], 'template' => ['cls' => 'bg-[#F3E9FF] text-[#5B3D8A]', 'label' => 'Custom · Template'], default => ['cls' => 'bg-wa-deep/10 text-wa-deep', 'label' => 'Custom · Text'], }; }; $tp = $typePill($row); @endphp
{{ __('Auto Reply / Analytics') }}
{{ __('Keyword') }} {{ __('analytics') }}
{{ $tag }}
{{ __('Keyword group') }}

{{ $keyword }}

{{ $matchSub }} {{ $tp['label'] }} {{ $deviceLabel }} @if ($statusActive) Active @else Paused @endif
@if ($row) Edit rule @endif
{{ __('Triggered') }} {{ __('all-time') }}
{{ number_format($totalTriggers) }} {{ __('replies sent') }}
{{ __('Last fired') }} {{ $lastFired ? $lastFired->format('M j H:i') : '—' }}
{{ $lastFired ? $lastFired->diffForHumans(['short' => true, 'parts' => 1]) : '—' }} {{ $lastFired ? 'ago' : 'never' }}
{{ __('Match method') }} {{ optional($row)->matching_method ?? '—' }}
{{ optional($row)->fuzzy_similarity ?? '—' }}{{ optional($row)->matching_method === 'fuzzy' ? '%' : '' }} {{ optional($row)->matching_method === 'fuzzy' ? 'similarity' : 'rule' }}
{{ __('Cooldown') }} {{ __('per contact') }}
{{ optional($row)->cooldown ?? '—' }} {{ optional($row)->cooldown ? 'sec' : 'no limit' }}
{{ __('Reply latency') }} {{ __('target 1.5 s') }}
{{ $analytics['latencyAvgMs'] !== null ? number_format($analytics['latencyAvgMs']) . ' ms' : '—' }} {{ $analytics['latencyP95Ms'] !== null ? 'p95 ' . number_format($analytics['latencyP95Ms']) . ' ms' : 'no data' }}
{{ __('Triggers over time') }}

{{ __('Daily firing pattern') }}

values()->all())'>
{{ __('Variant breakdown') }}

{{ __('Which spelling fires') }}

@php $variantTotal = max(1, $analytics['variantStats']->sum()); $variantDots = ['bg-wa-deep', 'bg-wa-teal', 'bg-accent-amber', 'bg-[#13478A]', 'bg-[#5B3D8A]']; @endphp @forelse ($analytics['variantStats'] as $variant => $count) @php $pct = round(($count / $variantTotal) * 100); @endphp
{{ $variant }} {{ number_format($count) }} / {{ $pct }}%
@empty @include('user.partials.empty-state', [ 'message' => 'No variants found. Variant performance appears after this rule starts firing.', ]) @endforelse
{{ __('When it fires') }}

{{ __('Hour-of-day heatmap') }}

{{ __('last 30 days') }}
{{ __('Conversion funnel') }}

{{ __('From keyword to reply') }}

@forelse ($analytics['funnel'] as $step)
{{ $step['label'] }} {{ number_format($step['count']) }}{{ $step['pct'] !== 100.0 ? ' / ' . $step['pct'] . '%' : '' }}
@empty @include('user.partials.empty-state', [ 'message' => 'No inbound traffic found on this device. Matching traffic will appear here after the rule fires.', ]) @endforelse
{{ __('Recent triggers') }}

{{ __('Last 8 firings') }}

{{ number_format($analytics['fired30d']) }} fires · last 30d
@forelse ($analytics['recent'] as $log) @empty @endforelse
{{ __('Time') }} {{ __('From') }} {{ __('Message') }} {{ __('Variant') }} {{ __('Latency') }} {{ __('Status') }}
{{ optional($log->fired_at)->format('M j H:i') }} {{ $maskPhone($log->contact_phone) }} "{{ \Illuminate\Support\Str::limit($log->matched_text, 60) }}" {{ $log->matched_variant ?: '—' }} Sent
@include('user.partials.empty-state', [ 'message' => 'No fires found. Once your bot starts matching this rule, fires will appear here.', ])
{{ __('Top users') }}

{{ __('Most-frequent senders') }}

@php $userGradients = [ 'from-wa-teal to-wa-deep', 'from-accent-amber to-accent-coral', 'from-wa-deep to-ink-900', 'from-[#5B3D8A] to-[#13478A]', 'from-[#7B5A14] to-accent-amber', ]; @endphp @forelse ($analytics['topUsers'] as $u)
{{ $phoneInitials($u['phone']) ?: '••' }}
{{ $maskPhone($u['phone']) }}
{{ __('contact') }}
{{ number_format($u['count']) }}×
@empty @include('user.partials.empty-state', [ 'message' => 'No senders found. Frequent senders will appear here after inbound traffic starts.', ]) @endforelse
{{ number_format($analytics['uniqueUsers']) }} unique {{ $analytics['uniqueUsers'] === 1 ? 'sender' : 'senders' }}
@php // Real health-check signals — derived from analytics already // computed by AutoReplyController::keyword(). When we have NO // lookup data for this device we skip the rate tiles entirely // rather than display fabricated percentages. $matchedThisPct = (float) ($analytics['funnel'][2]['pct'] ?? 0); $latencyAvg = $analytics['latencyAvgMs'] ?? null; $fired7d = (int) ($analytics['fired7d'] ?? 0); $fired30d = (int) ($analytics['fired30d'] ?? 0); $hasLookupData = ($analytics['funnel'][0]['count'] ?? 0) > 0; @endphp
{{ __('Health checks') }}

{{ __('Things to watch') }}

@if ($hasLookupData) @php $rateOk = $matchedThisPct >= 60; $tileClass = $rateOk ? 'bg-wa-deep/5 border-wa-deep/15' : 'bg-accent-amber/10 border-accent-amber/30'; $iconBg = $rateOk ? 'bg-wa-deep' : 'bg-accent-amber'; @endphp
{{ $rateOk ? __('Match rate is healthy') : __('Match rate is low') }}
{{ $matchedThisPct }}% {{ __('of incoming messages on this device matched this rule.') }}
@endif @if ($latencyAvg !== null) @php $latencyOk = $latencyAvg <= 1500; $latTileCls = $latencyOk ? 'bg-wa-deep/5 border-wa-deep/15' : 'bg-accent-amber/10 border-accent-amber/30'; $latIconBg = $latencyOk ? 'bg-wa-deep' : 'bg-accent-amber'; @endphp
{{ $latencyOk ? __('Latency under target') : __('Latency above target') }}
{{ number_format($latencyAvg / 1000, 2) }} s {{ __('server-side lookup avg.') }}
@endif
{{ number_format($fired7d) }} {{ __('fires in last 7 days') }}
{{ number_format($fired30d) }} {{ __('total in the last 30 days.') }}
@if (!$hasLookupData && $latencyAvg === null)
{{ __('Not enough activity yet') }}
{{ __('Health stats appear once this rule starts firing on real inbound messages.') }}
@endif
{{ __('Reply preview') }}
{{ __('What customers see') }}
Hi @{{ name }}! Thanks for reaching out — happy to help.
{{ __('Custom · Text · 53 chars') }}
Edit reply