import { useEffect, useMemo, useState } from "react"; import { useAuth } from "@/_core/hooks/useAuth"; import { trpc } from "@/lib/trpc"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Skeleton } from "@/components/ui/skeleton"; import { toast } from "sonner"; import { useBackgroundTask } from "@/hooks/useBackgroundTask"; import { Database, Image as ImageIcon, Loader2, Microscope, ShieldCheck, Sparkles } from "lucide-react"; type ReferenceImage = { id: number; slug: string; title: string; exerciseType: string; imageUrl: string; sourcePageUrl: string; sourceLabel: string; author: string | null; license: string | null; expectedFocus: string[] | null; tags: string[] | null; notes: string | null; }; type VisionRun = { id: number; taskId: string; userId: number; userName: string | null; referenceImageId: number | null; referenceTitle: string | null; title: string; exerciseType: string; imageUrl: string; status: "queued" | "succeeded" | "failed"; visionStatus: "pending" | "ok" | "fallback" | "failed"; configuredModel: string | null; expectedFocus: string[] | null; summary: string | null; corrections: string | null; warning: string | null; error: string | null; createdAt: Date; updatedAt: Date; }; function statusBadge(run: VisionRun) { if (run.status === "failed" || run.visionStatus === "failed") { return 失败; } if (run.status === "queued" || run.visionStatus === "pending") { return 排队中; } if (run.visionStatus === "fallback") { return 文本降级; } return 视觉成功; } export default function VisionLab() { const { user } = useAuth(); const utils = trpc.useUtils(); const [activeTaskId, setActiveTaskId] = useState(null); const activeTask = useBackgroundTask(activeTaskId); const libraryQuery = trpc.vision.library.useQuery(); const runsQuery = trpc.vision.runs.useQuery( { limit: 50 }, { refetchInterval: 4000 } ); const seedMutation = trpc.vision.seedLibrary.useMutation({ onSuccess: (data) => { toast.success(`标准图库已就绪,共 ${data.count} 张`); utils.vision.library.invalidate(); }, onError: (error) => toast.error(`标准图库初始化失败: ${error.message}`), }); const runReferenceMutation = trpc.vision.runReference.useMutation({ onSuccess: (data) => { setActiveTaskId(data.taskId); toast.success("视觉测试任务已提交"); utils.vision.runs.invalidate(); }, onError: (error) => toast.error(`视觉测试提交失败: ${error.message}`), }); const runAllMutation = trpc.vision.runAll.useMutation({ onSuccess: (data) => { toast.success(`已提交 ${data.count} 个视觉测试任务`); if (data.queued[0]?.taskId) { setActiveTaskId(data.queued[0].taskId); } utils.vision.runs.invalidate(); }, onError: (error) => toast.error(`批量视觉测试提交失败: ${error.message}`), }); useEffect(() => { if (activeTask.data?.status === "succeeded" || activeTask.data?.status === "failed") { utils.vision.runs.invalidate(); setActiveTaskId(null); } }, [activeTask.data, utils.vision.runs]); const references = useMemo(() => (libraryQuery.data ?? []) as ReferenceImage[], [libraryQuery.data]); const runs = useMemo(() => (runsQuery.data ?? []) as VisionRun[], [runsQuery.data]); if (libraryQuery.isLoading && runsQuery.isLoading) { return (
); } return (

视觉标准图库

用公网可访问的网球标准图验证多模态纠正链路,并持久化每次测试结果。

{user?.role === "admin" ? ( ) : null}
{user?.role === "admin" ? ( Admin 视角 当前账号可查看全部视觉测试记录。若用户名为 `H1` 且被配置进 `ADMIN_USERNAMES`,登录后会自动拥有此视角。 ) : ( 个人测试视角 当前页面展示标准图库,以及你自己提交的视觉测试结果。 )} {activeTask.data?.status === "queued" || activeTask.data?.status === "running" ? ( 后台执行中 {activeTask.data.message || "视觉测试正在后台执行。"} ) : null}

标准图片库

{references.length} 张
{references.map((reference) => (
{reference.title}
{reference.title} {reference.exerciseType} {reference.license ? {reference.license} : null} {reference.notes ? (

{reference.notes}

) : null} {reference.expectedFocus?.length ? (
{reference.expectedFocus.map((item) => ( {item} ))}
) : null}
来源页
))}

视觉测试记录

{runs.length} 条
{runs.map((run) => (

{run.title}

{statusBadge(run)} {run.exerciseType}

{new Date(run.createdAt).toLocaleString("zh-CN")} {user?.role === "admin" && run.userName ? ` · 提交人:${run.userName}` : ""}

{run.configuredModel ? ( {run.configuredModel} ) : null}
{run.summary ?

{run.summary}

: null} {run.warning ? (

降级说明:{run.warning}

) : null} {run.error ? (

错误:{run.error}

) : null} {run.expectedFocus?.length ? (
{run.expectedFocus.map((item) => ( {item} ))}
) : null} {run.corrections ? (
{run.corrections}
) : null}
))} {runs.length === 0 ? ( 还没有视觉测试记录。先运行一张标准图测试,结果会自动入库并显示在这里。 ) : null}
); }