"use client"; import * as React from "react"; import { useRouter } from "next/navigation"; import { useSession } from "next-auth/react"; import { useApi } from "@/lib/hooks/use-api"; import { monitoringApi, MonitoringRecord, MonitoringChangeReport } from "@/lib/api/monitoring"; import { alertsApi } from "@/lib/api/alerts"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { LoadingState, ErrorState, EmptyState } from "@/components/ui/api-states"; import { Bell, BellRing, AlertTriangle, CheckCheck, Settings, Clock, Filter, Activity, Play, Pause, RefreshCw, ChevronDown, ChevronUp, Eye, } from "lucide-react"; import { cn } from "@/lib/utils"; interface Alert { id: string; title: string; description?: string; type: string; severity: "critical" | "warning" | "info"; is_read: boolean; created_at: string; brand_id?: string; } interface AlertsResponse { items: Alert[]; total: number; } interface UnreadCountResponse { unread_count: number; } interface BrandsResponse { items: { id: string; name: string }[]; } function formatTimeAgo(dateString: string): string { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return "刚刚"; if (diffMins < 60) return `${diffMins}分钟前`; if (diffHours < 24) return `${diffHours}小时前`; if (diffDays < 7) return `${diffDays}天前`; return date.toLocaleDateString("zh-CN"); } function ChangeTypeBadge({ changeType }: { changeType: string | null }) { if (!changeType) return null; const config: Record = { positive: { variant: "default", label: "正向" }, negative: { variant: "destructive", label: "负向" }, neutral: { variant: "secondary", label: "中性" }, }; const c = config[changeType] || { variant: "outline" as const, label: changeType }; return {c.label}; } function RecordCard({ record, token, onRefresh, }: { record: MonitoringRecord; token: string; onRefresh: () => void; }) { const [expanded, setExpanded] = React.useState(false); const [report, setReport] = React.useState(null); const [reportLoading, setReportLoading] = React.useState(false); const [reportError, setReportError] = React.useState(null); const [checking, setChecking] = React.useState(false); const [togglingStatus, setTogglingStatus] = React.useState(false); const handleExpand = async () => { if (expanded) { setExpanded(false); return; } setExpanded(true); if (!report) { setReportLoading(true); setReportError(null); try { const result = await monitoringApi.getReport(token, record.id); setReport(result); } catch (err) { setReportError(err instanceof Error ? err.message : "加载报告失败"); } finally { setReportLoading(false); } } }; const handleTriggerCheck = async () => { setChecking(true); try { await monitoringApi.triggerCheck(token, record.id); onRefresh(); } catch { } finally { setChecking(false); } }; const handleToggleStatus = async () => { setTogglingStatus(true); try { const newStatus = record.status === "active" ? "paused" : "active"; await monitoringApi.updateStatus(token, record.id, { status: newStatus }); onRefresh(); } catch { } finally { setTogglingStatus(false); } }; return (

{record.platform || "全平台"}

{record.status === "paused" && ( 已暂停 )}
{record.last_checked_at ? formatTimeAgo(record.last_checked_at) : "未检测"} 关键词: {record.query_keywords}
{expanded ? ( ) : ( )}
{expanded && (
{reportLoading && } {reportError && } {report && !reportLoading && !reportError && ( <>

基线指标

引用次数 {String(report.baseline.citation_count ?? "—")}
情感分数 {report.baseline.sentiment != null ? String(report.baseline.sentiment) : "—"}
排名 {report.baseline.rank != null ? String(report.baseline.rank) : "—"}

当前指标

引用次数 {String(report.current.citation_count ?? "—")}
情感分数 {report.current.sentiment != null ? String(report.current.sentiment) : "—"}
排名 {report.current.rank != null ? String(report.current.rank) : "—"}
{report.recommendations && report.recommendations.length > 0 && (

优化建议

    {report.recommendations.map((rec, i) => (
  • {rec}
  • ))}
)} )}
)}
); } function MonitoringRecordsTab() { const { data: session } = useSession(); const token = session?.accessToken || ""; const { data: brandsData } = useApi("/api/v1/brands/?limit=1"); const brandId = brandsData?.items?.[0]?.id ?? null; const { data: recordsData, isLoading, error, refresh, } = useApi<{ records: MonitoringRecord[]; total: number }>( token && brandId ? `/api/v1/monitoring/brand/${brandId}` : null ); const records = recordsData?.records ?? []; if (isLoading) { return ; } if (error) { return ; } if (records.length === 0) { return ( } message="暂无监测记录" description="创建监测任务后将在此显示监测结果" /> ); } return (
{records.map((record) => ( ))}
); } interface StatCardProps { title: string; value: number | string; icon: React.ReactNode; trend?: string; color: "emerald" | "amber" | "red" | "blue"; } function StatCard({ title, value, icon, trend, color }: StatCardProps) { const colorMap = { emerald: { bg: "bg-emerald-50", icon: "text-emerald-600", border: "border-emerald-100" }, amber: { bg: "bg-amber-50", icon: "text-amber-600", border: "border-amber-100" }, red: { bg: "bg-red-50", icon: "text-red-600", border: "border-red-100" }, blue: { bg: "bg-blue-50", icon: "text-blue-600", border: "border-blue-100" }, }; const colors = colorMap[color]; return (

{title}

{value}

{trend &&

{trend}

}
{icon}
); } interface AlertRowProps { alert: Alert; onMarkRead: (id: string) => void; isMutating: boolean; } function AlertRow({ alert, onMarkRead, isMutating }: AlertRowProps) { const severityConfig = { critical: { badge: "destructive", label: "严重", icon: }, warning: { badge: "default", label: "警告", icon: }, info: { badge: "secondary", label: "信息", icon: }, }; const typeMap: Record = { citation_drop: "引用下降", ranking_drop: "排名下降", new_competitor: "新竞争对手", content_update: "内容更新", system: "系统通知", }; const config = severityConfig[alert.severity]; const timeAgo = formatTimeAgo(alert.created_at); return (
{config.icon}

{alert.title}

{!alert.is_read && ( )}
{timeAgo}
{alert.description && (

{alert.description}

)}
{config.label} {typeMap[alert.type] || alert.type}
{!alert.is_read && ( )}
); } function AlertsTab() { const [filterType, setFilterType] = React.useState("all"); const [filterSeverity, setFilterSeverity] = React.useState("all"); const [filterRead, setFilterRead] = React.useState("all"); const { data: alertsData, isLoading: alertsLoading, error: alertsError, refresh: refreshAlerts, } = useApi("/api/v1/alerts?limit=50"); const { data: unreadData, isLoading: unreadLoading, refresh: refreshUnread, } = useApi("/api/v1/alerts/unread-count"); const [mutatingId, setMutatingId] = React.useState(null); const [mutatingAll, setMutatingAll] = React.useState(false); const handleMarkRead = async (alertId: string) => { try { setMutatingId(alertId); await alertsApi.markRead(alertId); refreshAlerts(); refreshUnread(); } finally { setMutatingId(null); } }; const handleMarkAllRead = async () => { try { setMutatingAll(true); await alertsApi.markAllRead(); refreshAlerts(); refreshUnread(); } finally { setMutatingAll(false); } }; const alerts = alertsData?.items ?? []; const unreadCount = unreadData?.unread_count ?? 0; const filteredAlerts = alerts.filter((alert) => { if (filterType !== "all" && alert.type !== filterType) return false; if (filterSeverity !== "all" && alert.severity !== filterSeverity) return false; if (filterRead === "unread" && alert.is_read) return false; if (filterRead === "read" && !alert.is_read) return false; return true; }); const criticalCount = alerts.filter((a) => a.severity === "critical").length; const todayCount = alerts.filter((a) => { const date = new Date(a.created_at); const today = new Date(); return date.toDateString() === today.toDateString(); }).length; const processedCount = alerts.filter((a) => a.is_read).length; const uniqueTypes = Array.from(new Set(alerts.map((a) => a.type))); if (alertsLoading || unreadLoading) { return ; } if (alertsError) { return ; } return (
} trend="需要处理" color="blue" /> } trend="高优先级" color="red" /> } trend="今天" color="amber" /> } trend="已读" color="emerald" />
告警列表 {alerts.length > 0 && ( {alerts.length} )} {unreadCount > 0 && ( )}
{uniqueTypes.length > 0 && ( )}
{filteredAlerts.length === 0 ? ( } message="暂无告警" description="当前没有符合条件的告警记录" /> ) : (
{filteredAlerts.map((alert) => ( ))}
)}
); } export default function MonitoringPage() { const router = useRouter(); return (

监测优化

实时监控品牌AI可见性,及时响应告警通知

监测记录 告警通知
); }