"use client"; import { useState, useEffect, useCallback } from "react"; import { useParams } from "next/navigation"; import { Card, CardContent } from "@/components/ui/card"; interface TaskLogItem { id: number; taskKey: string; status: string; startedAt: string; finishedAt: string | null; duration: number | null; result: Record | null; error: string | null; } const TASK_META: Record = { "task1-discover-tools": { name: "发现AI工具", desc: "每天从多源搜索新AI工具,过滤去重后入库等待审核", cron: "0 6 * * *", metrics: ["发现工具数", "通过检查数", "入库数"] }, "task3-check-tools": { name: "工具巡检", desc: "检查所有已入库工具的网站可用性、Logo完整性,自动发布合格工具", cron: "0 3 * * *", metrics: ["巡检总数", "网站失效数", "Logo异常数", "自动发布数"] }, "task4-discover-skills": { name: "发现AI技能", desc: "从GitHub搜索热门AI开源项目,获取README并入库等待审核", cron: "0 2 * * *", metrics: ["搜索关键词数", "搜索结果数", "新增入库数"] }, "task5-update-stars": { name: "更新星值", desc: "更新所有GitHub技能的最新Stars数,计算日/周/月变化趋势", cron: "0 1 * * *", metrics: ["总项目数", "成功更新数", "失败数"] }, "task6-review-hot": { name: "评测热门", desc: "对未评测的最热门2个AI技能进行五维深度评测并自动发布", cron: "0 4 * * *", metrics: ["候选技能数", "评测完成数"] }, "daily-news": { name: "AI日报生成", desc: "搜索真实新闻素材,AI整理格式生成当日AI日报", cron: "30 7 * * *", metrics: ["新闻源数", "获取素材数", "最终条目数"] }, }; export default function TaskDetailPage() { const params = useParams(); const taskKey = params.taskKey as string; const meta = TASK_META[taskKey] || { name: taskKey, desc: "", cron: "", metrics: [] }; const [logs, setLogs] = useState([]); const [config, setConfig] = useState<{ enabled: boolean; cronExpr: string }>({ enabled: true, cronExpr: meta.cron }); const [loading, setLoading] = useState(true); const [running, setRunning] = useState(false); const fetchData = useCallback(async () => { setLoading(true); try { const [lRes, cRes] = await Promise.all([ fetch(`/api/admin/tasks/logs?taskKey=${taskKey}&pageSize=30`), fetch("/api/admin/tasks/configs"), ]); if (lRes.ok) setLogs((await lRes.json()).logs || []); if (cRes.ok) { const tasks = (await cRes.json()).tasks || []; const t = tasks.find((x: { taskKey: string }) => x.taskKey === taskKey); if (t) setConfig({ enabled: t.enabled, cronExpr: t.cronExpr }); } } catch {} setLoading(false); }, [taskKey]); useEffect(() => { fetchData(); }, [fetchData]); const handleRun = async () => { setRunning(true); try { const res = await fetch("/api/admin/tasks/run", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ taskKey }), }); const data = await res.json(); alert(`执行${data.status === "completed" ? "完成" : "失败"}\n${(data.output || "").slice(-300)}`); } catch (e: unknown) { alert(`错误: ${(e as Error).message}`); } setRunning(false); fetchData(); }; const successCount = logs.filter((l) => l.status === "completed").length; const failCount = logs.filter((l) => l.status === "failed").length; return (

{meta.name} {config.enabled ? "已启用" : "已停用"}

{meta.desc}

{config.cronExpr || "未配置"}
{logs.length}
总执行次数
{successCount}
成功
{failCount}
失败
{Math.round((successCount / Math.max(logs.length, 1)) * 100)}%
成功率
{loading ? (
{Array.from({ length: 5 }).map((_, i) => (
))}
) : logs.length === 0 ? (

暂无执行记录,点击"手动执行"触发首次运行

) : (
{logs.map((log) => { const isSuccess = log.status === "completed"; const isFailed = log.status === "failed"; return (
{isSuccess ? "成功" : isFailed ? "失败" : log.status}
{new Date(log.startedAt).toLocaleString("zh-CN")} {log.duration && 耗时: {(log.duration / 1000).toFixed(1)}s}
{log.result && (
                      {typeof log.result === "string" ? log.result : JSON.stringify(log.result, null, 2)}
                    
)} {log.error && (

{log.error}

)}
); })}
)}
); }