import { useEffect, useState } from 'react' import { apiFetch } from '@core/lib/api' import { Card, CardContent, CardHeader, CardTitle } from '@core/components/ui/card' import { Skeleton } from '@core/components/ui/skeleton' import { Bot, CheckCircle, AlertTriangle, ThumbsDown, BookOpen, RefreshCw } from 'lucide-react' interface AIMetrics { ai_attempted?: number ai_resolved?: number human_escalations?: number thumbs_down_rate?: number kb_articles_created?: number kb_reuse_count?: number } interface FeedbackItem { id: string created_at: string data?: { kb_candidate?: boolean customer_feedback?: string thread_id?: string } } const METRIC_CARDS = [ { key: 'ai_attempted' as keyof AIMetrics, label: 'AI Attempted', icon: Bot, color: 'text-purple-600', bg: 'bg-purple-50', format: (v: number) => v.toLocaleString(), }, { key: 'ai_resolved' as keyof AIMetrics, label: 'AI Resolved', icon: CheckCircle, color: 'text-green-600', bg: 'bg-green-50', format: (v: number) => v.toLocaleString(), }, { key: 'human_escalations' as keyof AIMetrics, label: 'Human Escalations', icon: AlertTriangle, color: 'text-amber-600', bg: 'bg-amber-50', format: (v: number) => v.toLocaleString(), }, { key: 'thumbs_down_rate' as keyof AIMetrics, label: 'Not-Helpful Rate', icon: ThumbsDown, color: 'text-red-600', bg: 'bg-red-50', format: (v: number) => `${(v * 100).toFixed(1)}%`, }, { key: 'kb_articles_created' as keyof AIMetrics, label: 'KB Articles Created', icon: BookOpen, color: 'text-blue-600', bg: 'bg-blue-50', format: (v: number) => v.toLocaleString(), }, { key: 'kb_reuse_count' as keyof AIMetrics, label: 'KB Reuse Count', icon: RefreshCw, color: 'text-indigo-600', bg: 'bg-indigo-50', format: (v: number) => v.toLocaleString(), }, ] export default function AIDashboardPage() { const [metrics, setMetrics] = useState(null) const [metricsLoading, setMetricsLoading] = useState(true) const [gaps, setGaps] = useState([]) const [gapsLoading, setGapsLoading] = useState(true) useEffect(() => { apiFetch('/api/observability?action=ai_support_metrics') .then(r => r.json()) .then(j => setMetrics(j?.metrics ?? j?.data?.metrics ?? j ?? null)) .catch(() => setMetrics(null)) .finally(() => setMetricsLoading(false)) }, []) useEffect(() => { apiFetch('/api/admin-data?entity=items&type_slug=ai_feedback_record&limit=20') .then(r => r.json()) .then(j => { const all: FeedbackItem[] = Array.isArray(j?.data) ? j.data : Array.isArray(j) ? j : [] setGaps(all.filter(item => item.data?.kb_candidate === false && item.data?.customer_feedback === 'not_helpful')) }) .catch(() => setGaps([])) .finally(() => setGapsLoading(false)) }, []) return (

AI Dashboard

AI support performance metrics

{/* Metric Cards — 2×3 grid */}
{METRIC_CARDS.map(card => { const Icon = card.icon const rawValue = metrics?.[card.key] const value = rawValue != null ? card.format(rawValue as number) : null return (
{card.label}
{metricsLoading ? ( ) : (

{value ?? '—'}

)}
) })}
{/* Knowledge Gaps */}

Knowledge Gaps

AI feedback records where no KB article was created and the customer marked the response as not helpful.

{gapsLoading ? (
{Array.from({ length: 4 }).map((_, i) => )}
) : gaps.length === 0 ? (

No knowledge gaps detected.

) : (
{gaps.map(item => (

{item.data?.thread_id ? `Thread: ${String(item.data.thread_id).slice(0, 8)}…` : `Record: ${item.id.slice(0, 8)}…`}

{new Date(item.created_at).toLocaleDateString()}

not helpful
))}
)}
) }