import { BarChart3, BrainCircuit, Clock3, RefreshCcw, Search } from 'lucide-react' import { startTransition, useCallback, useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { adminApi, ApiError } from '@/lib/api' import type { AdminAnalyticsResponse } from '@/lib/types' function StatCard({ label, value, note, icon: Icon, }: { label: string value: string note: string icon: typeof Search }) { return (

{label}

{value}

{note}

) } function formatEventType(value: string) { return value === 'ai_question' ? 'AI 问答' : '站内搜索' } function formatSuccess(value: boolean | null) { if (value === null) { return '未记录' } return value ? '成功' : '失败' } export function AnalyticsPage() { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const loadAnalytics = useCallback(async (showToast = false) => { try { if (showToast) { setRefreshing(true) } const next = await adminApi.analytics() startTransition(() => { setData(next) }) if (showToast) { toast.success('数据分析已刷新。') } } catch (error) { if (error instanceof ApiError && error.status === 401) { return } toast.error(error instanceof ApiError ? error.message : '无法加载数据分析。') } finally { setLoading(false) setRefreshing(false) } }, []) useEffect(() => { void loadAnalytics(false) }, [loadAnalytics]) const maxDailyTotal = useMemo(() => { if (!data?.daily_activity.length) { return 1 } return Math.max( ...data.daily_activity.map((item) => item.searches + item.ai_questions), 1, ) }, [data]) if (loading || !data) { return (
{Array.from({ length: 4 }).map((_, index) => ( ))}
) } const statCards = [ { label: '累计搜索', value: String(data.overview.total_searches), note: `近 7 天 ${data.overview.searches_last_7d} 次,平均命中 ${data.overview.avg_search_results_last_7d.toFixed(1)} 条`, icon: Search, }, { label: '累计 AI 提问', value: String(data.overview.total_ai_questions), note: `近 7 天 ${data.overview.ai_questions_last_7d} 次`, icon: BrainCircuit, }, { label: '24 小时活跃', value: String(data.overview.searches_last_24h + data.overview.ai_questions_last_24h), note: `搜索 ${data.overview.searches_last_24h} / AI ${data.overview.ai_questions_last_24h}`, icon: Clock3, }, { label: '近 7 天去重词', value: String( data.overview.unique_search_terms_last_7d + data.overview.unique_ai_questions_last_7d, ), note: `搜索 ${data.overview.unique_search_terms_last_7d} / AI ${data.overview.unique_ai_questions_last_7d}`, icon: BarChart3, }, ] return (
数据分析

前台搜索与 AI 问答洞察

这里会记录用户真实提交过的站内搜索词和 AI 提问,方便你判断内容需求、热点问题和接入质量。

{statCards.map((item) => ( ))}
最近记录 最近一批真实发生的搜索和 AI 问答请求。
{data.recent_events.length} 条
类型 内容 结果 时间 {data.recent_events.map((event) => (
{formatEventType(event.event_type)} {event.response_mode ? (

{event.response_mode}

) : null}

{event.query}

{event.provider ? `${event.provider}` : '未记录渠道'} {event.chat_model ? ` / ${event.chat_model}` : ''}

{formatSuccess(event.success)}
{event.result_count !== null ? `${event.result_count} 条/源` : '无'}
{event.latency_ms !== null ? (
{event.latency_ms} ms
) : null}
{event.created_at}
))}
热门搜索词 近 7 天最常被搜索的关键词。
{data.top_search_terms.length} 个
{data.top_search_terms.length ? ( data.top_search_terms.map((item) => (

{item.query}

{item.count}

最近一次:{item.last_seen_at}

)) ) : (

最近 7 天还没有站内搜索记录。

)}
热门 AI 问题 近 7 天重复出现最多的提问。
{data.top_ai_questions.length} 个
{data.top_ai_questions.length ? ( data.top_ai_questions.map((item) => (

{item.query}

{item.count}

最近一次:{item.last_seen_at}

)) ) : (

最近 7 天还没有 AI 提问记录。

)}
分析侧栏 24 小时、7 天和模型渠道维度的快速摘要。

24 小时搜索

{data.overview.searches_last_24h}

24 小时 AI 提问

{data.overview.ai_questions_last_24h}

AI 平均耗时

{data.overview.avg_ai_latency_ms_last_7d !== null ? `${Math.round(data.overview.avg_ai_latency_ms_last_7d)} ms` : '暂无'}

统计范围:最近 7 天

模型渠道分布 最近 7 天 AI 请求实际使用的 provider 厂商。 {data.providers_last_7d.length ? ( data.providers_last_7d.map((item) => (
{item.provider} {item.count}
)) ) : (

最近 7 天还没有 AI 渠道数据。

)}
7 天走势 搜索与 AI 问答的日维度活动量。 {data.daily_activity.map((item) => { const total = item.searches + item.ai_questions const width = `${Math.max((total / maxDailyTotal) * 100, total > 0 ? 12 : 0)}%` return (
{item.date} 搜索 {item.searches} / AI {item.ai_questions}
) })}
) }