"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { MetricCard, StageProgress } from "@/components/business"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Sparkles, Search, FileText, Send, BarChart3, Target, Plus, ArrowRight, Zap, Lock, Loader2, CheckCircle2, XCircle, Clock, } from "lucide-react"; import { type GeoProject, type LifecycleStats } from "@/lib/api"; import { agentsApi, type AgentTask } from "@/lib/api/agents"; import { useSession } from "next-auth/react"; import { useApi } from "@/lib/hooks/use-api"; import { LoadingState, ErrorState, EmptyState, } from "@/components/ui/api-states"; import { SubscriptionStatus } from "@/components/subscription/SubscriptionStatus"; import { UsageProgress } from "@/components/subscription/UsageProgress"; import { ROICard } from "@/components/dashboard/ROICard"; /* ─── Helpers ─────────────────────────────────────────────────────────────────*/ const STAGE_CONFIG = [ { id: "diagnosis", label: "诊断分析" }, { id: "strategy", label: "策略制定" }, { id: "content", label: "内容生产" }, { id: "publishing", label: "分发执行" }, { id: "monitoring", label: "监测优化" }, ]; function buildStages(currentStage: GeoProject["current_stage"]) { const currentIndex = STAGE_CONFIG.findIndex((s) => s.id === currentStage); return STAGE_CONFIG.map((stage, idx) => { let status: "completed" | "active" | "pending" | "error" = "pending"; if (idx < currentIndex) status = "completed"; else if (idx === currentIndex) status = "active"; return { id: stage.id, label: stage.label, status }; }); } function getRecommendation(stage: GeoProject["current_stage"]) { const map: Record< GeoProject["current_stage"], { title: string; description: string; icon: React.ReactNode; href: string } > = { diagnosis: { title: "开始诊断分析", description: "诊断您品牌在AI搜索中的可见性现状", icon: , href: "/dashboard/diagnosis", }, strategy: { title: "生成优化策略", description: "基于诊断结果制定GEO+SEO优化方案", icon: , href: "/dashboard/strategy", }, content: { title: "创建新内容", description: "使用AI Agent批量生成优化内容", icon: , href: "/dashboard/content", }, publishing: { title: "配置分发渠道", description: "将内容分发到各大AI平台和搜索引擎", icon: , href: "/dashboard/publishing", }, monitoring: { title: "查看监测报告", description: "分析内容分发后的引用和排名数据", icon: , href: "/dashboard/monitoring", }, }; return map[stage]; } /* ─── Agent Activity Component ───────────────────────────────────────────────*/ const TASK_STATUS_CONFIG: Record< string, { label: string; icon: React.ReactNode; color: string } > = { pending: { label: "等待中", icon: , color: "bg-gray-100 text-gray-600", }, running: { label: "运行中", icon: , color: "bg-blue-100 text-blue-600", }, completed: { label: "已完成", icon: , color: "bg-emerald-100 text-emerald-600", }, failed: { label: "失败", icon: , color: "bg-red-100 text-red-600", }, cancelled: { label: "已取消", icon: , color: "bg-yellow-100 text-yellow-600", }, }; function formatDuration(startedAt?: string, completedAt?: string): string { if (!startedAt) return "-"; const start = new Date(startedAt).getTime(); const end = completedAt ? new Date(completedAt).getTime() : Date.now(); const seconds = Math.round((end - start) / 1000); if (seconds < 60) return `${seconds}s`; const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}m ${remainingSeconds}s`; } function formatRelativeTime(dateStr: string): string { const now = Date.now(); const then = new Date(dateStr).getTime(); const diffMs = now - then; const diffMin = Math.floor(diffMs / 60000); if (diffMin < 1) return "刚刚"; if (diffMin < 60) return `${diffMin}分钟前`; const diffHour = Math.floor(diffMin / 60); if (diffHour < 24) return `${diffHour}小时前`; const diffDay = Math.floor(diffHour / 24); return `${diffDay}天前`; } function AgentActivity() { const { data: session } = useSession(); const token = (session as { accessToken?: string })?.accessToken; const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { if (!token) return; agentsApi .listTasks(token, { limit: 5 }) .then((result) => setTasks(result.items ?? [])) .catch(() => setTasks([])) .finally(() => setLoading(false)); }, [token]); return ( Agent活动 查看全部 {loading ? ( ) : tasks.length === 0 ? ( 暂无执行记录 ) : ( {tasks.map((task) => { const cfg = TASK_STATUS_CONFIG[task.status] ?? { label: task.status, icon: , color: "bg-gray-100 text-gray-600", }; return ( {task.task_type} {cfg.icon} {cfg.label} {formatDuration(task.started_at, task.completed_at)} {task.created_at ? formatRelativeTime(task.created_at) : ""} ); })} )} ); } /* ─── Component ───────────────────────────────────────────────────────────────*/ export default function DashboardPage() { const router = useRouter(); const { data: projects, isLoading: projectsLoading, error: projectsError, refresh: refreshProjects, } = useApi("/api/v1/lifecycle/projects/"); const { data: stats, isLoading: statsLoading, error: statsError, refresh: refreshStats, } = useApi("/api/v1/lifecycle/projects/stats"); const loading = projectsLoading || statsLoading; // "用户未关联组织" 类错误视为空状态 const isOrgError = (err: Error | undefined) => err?.message.includes("未关联组织") || err?.message.includes("No organization"); const hasOrgError = isOrgError(projectsError) || isOrgError(statsError); const error = !hasOrgError && (projectsError || statsError) ? projectsError || statsError : undefined; const safeProjects: GeoProject[] = hasOrgError ? [] : (projects ?? []); const safeStats: LifecycleStats | null = hasOrgError ? null : (stats ?? null); const handleRetry = () => { refreshProjects(); refreshStats(); }; /* ─── Loading State ────────────────────────────────────────────────────────*/ if (loading) { return ( ); } /* ─── Error State ──────────────────────────────────────────────────────────*/ if (error) { return ( 品牌健康中心 ); } /* ─── Empty State ──────────────────────────────────────────────────────────*/ if (safeProjects.length === 0) { return ( 品牌健康中心 GEO和SEO是AI营销时代的共生体 } message="开始优化您的AI可见性" description="创建第一个项目,系统将自动引导您完成从诊断分析到监测优化的全生命周期管理。" action={ 创建项目 } /> ); } /* ─── Active Dashboard ─────────────────────────────────────────────────────*/ const project = safeProjects[0]; const stages = buildStages(project.current_stage); const recommendation = getRecommendation(project.current_stage); const citationRate = safeStats?.avg_ai_citation_rate != null ? `${(safeStats.avg_ai_citation_rate * 100).toFixed(1)}%` : "—"; const userPlan = "free"; const planExpiresAt = undefined; const usageData = { queries: { current: 2, limit: 3 }, brands: { current: 1, limit: 1 }, alerts: { current: 0, limit: 0 }, }; const roiData = { roiPercentage: 0, valueGenerated: 0, subscriptionCost: 0, }; const isFreePlan = userPlan === "free"; return ( {/* Subscription Status Bar */} {/* 1. Page Title */} 品牌健康中心 {project.brand_name} — {project.name} 创建项目 {/* 2. KPI Cards */} {/* ROI Card + Feature Lock */} {isFreePlan && ( 解锁更多功能 {[ { label: "多品牌监控", desc: "同时监控3个以上品牌" }, { label: "无限告警", desc: "不限制告警通知数量" }, { label: "竞品对比", desc: "完整的竞品雷达图分析" }, { label: "AI优化建议", desc: "基于DeepSeek的个性化建议" }, ].map((feature) => ( {feature.label} {feature.desc} ))} 升级解锁全部功能 )} {/* 3. Stage Progress */} 生命周期进度 当前阶段: {STAGE_CONFIG.find((s) => s.id === project.current_stage)?.label} { router.push(`/dashboard/${stage.id}`); }} /> {/* 4. Two-column: Recommendation + Agent Activity */} {/* Recommendation */} 推荐下一步 {recommendation.icon} {recommendation.title} {recommendation.description} router.push(recommendation.href)} > 执行 router.push("/dashboard/agents")} > 管理Agent router.push("/dashboard/lifecycle")} > 项目详情 {/* Agent Activity */} ); }
Agent活动
暂无执行记录
GEO和SEO是AI营销时代的共生体
{project.brand_name} — {project.name}
解锁更多功能
{feature.label}
{feature.desc}
生命周期进度
推荐下一步
{recommendation.title}
{recommendation.description}