import { useSendToAgentChat } from "@agent-native/core/client/agent-chat"; import { appPath } from "@agent-native/core/client/api-path"; import { callAction, useActionMutation, useChangeVersions, } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { ShareButton } from "@agent-native/core/client/sharing"; import { IconRefresh, IconTrash, IconClock, IconArrowLeft, IconDatabase, IconHistory, IconLock, IconUsersGroup, IconWorld, } from "@tabler/icons-react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useMemo, useState } from "react"; import { useParams } from "react-router"; import { Link, useNavigate } from "react-router"; import { AnalysisHistoryPanel } from "@/components/analysis/AnalysisHistoryPanel"; import { useSetPageTitle, useSetHeaderActions, } from "@/components/layout/HeaderActions"; import Markdown from "@/components/Markdown"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { incrementItemView } from "@/lib/item-popularity"; import { analysisDetailPrefetchKey, type PrefetchSnapshot, } from "@/lib/prefetch-keys"; import { resourceCanEdit, resourceCanManage, type ResourceAccess, } from "@/lib/resource-access"; import { cn } from "@/lib/utils"; import LegacyFusionAnalysis, { isLegacyFusionAnalysis, } from "./LegacyFusionAnalysis"; interface Analysis extends ResourceAccess { id: string; name: string; description: string; question: string; instructions: string; dataSources: string[]; resultMarkdown: string; resultData: Record | null; createdAt: string; updatedAt: string; author: string; visibility: "private" | "org" | "public"; } async function fetchAnalysis(id: string): Promise { try { const data = await callAction("get-analysis", { id }, { method: "GET" }); if (!data || typeof data !== "object") return null; const a = data as Record; if (a.error) return null; return a as unknown as Analysis; } catch { return null; } } function formatDate(iso: string): string { return new Date(iso).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", }); } export default function AnalysisDetail() { const t = useT(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { send, isGenerating, codeRequiredDialog } = useSendToAgentChat(); const [historyOpen, setHistoryOpen] = useState(false); const analysesSync = useChangeVersions(["analyses", "action"]); const { data: analysis, isLoading } = useQuery({ queryKey: ["analysis-detail", id, analysesSync], queryFn: () => fetchAnalysis(id!), enabled: !!id, staleTime: 10_000, placeholderData: (prev) => prev, initialData: () => { if (!id) return undefined; const snapshot = queryClient.getQueryData< PrefetchSnapshot >(analysisDetailPrefetchKey(id)); if (snapshot?.data === null && snapshot.syncVersion !== analysesSync) { return undefined; } return snapshot?.data; }, initialDataUpdatedAt: () => { if (!id) return undefined; const queryKey = analysisDetailPrefetchKey(id); const snapshot = queryClient.getQueryData>(queryKey); if (!snapshot) return undefined; if (snapshot.syncVersion !== analysesSync) return 0; return queryClient.getQueryState(queryKey)?.dataUpdatedAt; }, }); const canEdit = resourceCanEdit(analysis); const canManage = resourceCanManage(analysis); const { mutateAsync: deleteAnalysis } = useActionMutation("delete-analysis", { method: "DELETE", }); useEffect(() => { if (analysis?.id) incrementItemView("analysis", analysis.id); }, [analysis?.id]); const handleRerun = () => { if (!analysis || !canEdit) return; send({ message: t("analyses.rerunMessage", { name: analysis.name }), context: `This is a re-run of a legacy saved ad-hoc analysis. Treat the refreshed result as a dashboard artifact: REAL_DATA_REQUIRED: run at least one real data-source query action before saving or answering; data-source-status and generate-chart do not count as data queries. If no source can answer, report the exact unavailable/error result instead of saving guessed results. If the report needs bespoke UI, create an extension and immediately embed it in a dashboard panel with config.extensionId. Only call save-analysis when the user explicitly asks to preserve this legacy analysis record.\n\n` + `Use these instructions to reproduce it:\n\n` + `Analysis ID: ${analysis.id}\n` + `Original question: ${analysis.question}\n\n` + `Instructions:\n${analysis.instructions}\n\n` + `After gathering the data, call update-dashboard with a new dashboardId to save the refreshed artifact. If the user explicitly asked to update this legacy record instead, call save-analysis with id="${analysis.id}".`, submit: true, }); }; const handleDelete = async () => { if (!id || !canManage) return; await deleteAnalysis({ id }); queryClient.removeQueries({ queryKey: analysisDetailPrefetchKey(id) }); queryClient.invalidateQueries({ queryKey: ["analyses-list"] }); navigate("/analyses"); }; const analysisShareUrl = useMemo(() => { if (!analysis?.id || typeof window === "undefined") return undefined; return window.location.origin + appPath("/analyses/" + analysis.id); }, [analysis?.id]); useSetPageTitle( analysis ? (

{analysis.name}

) : null, ); useSetHeaderActions( analysis ? ( <> {canEdit ? ( {t("analyses.rerunTooltip")} ) : null} {canManage ? ( {t("analyses.deleteTitle")} {t("analyses.deleteDescription", { name: analysis.name })} {t("sidebar.cancel")} {t("sidebar.delete")} ) : null} ) : null, ); if (isLoading) { return (
); } if (!analysis) { return (

{t("analyses.notFound")}

{t("analyses.mayHaveBeenDeleted")}

{t("analyses.backToAnalyses")}
); } const showLegacyFusionDashboard = isLegacyFusionAnalysis(analysis.id); return ( <> {codeRequiredDialog}
{/* Header */}
{t("analyses.allAnalyses")} {analysis.description && (

{analysis.description}

)} {/* Metadata bar */}
{t("analyses.updated", { date: formatDate(analysis.updatedAt) })} {analysis.createdAt !== analysis.updatedAt && ( {t("analyses.created", { date: formatDate(analysis.createdAt), })} )} {analysis.author && ( {t("analyses.byAuthor", { author: analysis.author })} )} {analysis.visibility === "public" ? ( ) : analysis.visibility === "org" ? ( ) : ( )} {analysis.visibility === "public" ? t("analyses.public") : analysis.visibility === "org" ? t("analyses.sharedWithOrg") : t("analyses.private")}
{/* Data source badges */} {analysis.dataSources?.length > 0 && (
{analysis.dataSources.map((ds) => ( {ds} ))}
)}
{/* Results */} {showLegacyFusionDashboard ? ( ) : (
)}
); }