import { useState, useEffect, type ReactNode } from "react"; import { useFetcher } from "@remix-run/react"; import { AlertCircle, Loader2 } from "lucide-react"; import { Badge, BadgeColor } from "../ui/badge"; import { type LogItem } from "~/hooks/use-logs"; import Markdown from "react-markdown"; import { getIconForAuthorise } from "../icon-utils"; import { cn, formatString } from "~/lib/utils"; import { getStatusColor } from "./utils"; import { format } from "date-fns"; interface LogDetailsProps { log: LogItem; } interface PropertyItemProps { label: string; value?: string | ReactNode; icon?: ReactNode; variant?: "default" | "secondary" | "outline" | "status"; statusColor?: string; className?: string; } function PropertyItem({ label, value, icon, variant = "secondary", statusColor, className, }: PropertyItemProps) { if (!value) return null; return (
{label} {variant === "status" ? ( {statusColor && ( )} {value} ) : ( {icon} {value} )}
); } interface EpisodeFact { uuid: string; fact: string; createdAt: string; validAt: string; attributes: any; } interface EpisodeFactsResponse { facts: EpisodeFact[]; invalidFacts: EpisodeFact[]; } function getStatusValue(status: string) { if (status === "PENDING") { return "In Queue"; } return status; } export function LogDetails({ log }: LogDetailsProps) { const [facts, setFacts] = useState([]); const [invalidFacts, setInvalidFacts] = useState([]); const [factsLoading, setFactsLoading] = useState(false); const fetcher = useFetcher(); // Fetch episode facts when dialog opens and episodeUUID exists useEffect(() => { if (facts.length === 0) { if (log.data?.type === "DOCUMENT" && log.data?.episodes?.length > 0) { setFactsLoading(true); // Fetch facts for all episodes in DOCUMENT type Promise.all( log.data.episodes.map((episodeId: string) => fetch(`/api/v1/episodes/${episodeId}/facts`).then((res) => res.json(), ), ), ) .then((results) => { const allFacts = results.flatMap((result) => result.facts || []); const allInvalidFacts = results.flatMap( (result) => result.invalidFacts || [], ); setFacts(allFacts); setInvalidFacts(allInvalidFacts); setFactsLoading(false); }) .catch(() => { setFactsLoading(false); }); } else if (log.episodeUUID) { setFactsLoading(true); fetcher.load(`/api/v1/episodes/${log.episodeUUID}/facts`); } } }, [log.episodeUUID, log.data?.type, log.data?.episodes, facts.length]); // Handle fetcher response useEffect(() => { if (fetcher.data && fetcher.state === "idle") { setFactsLoading(false); const response = fetcher.data; setFacts(response.facts); setInvalidFacts(response.invalidFacts); } }, [fetcher.data, fetcher.state]); return (
Episode Details
{log.data?.type === "DOCUMENT" && log.data?.episodes ? ( {log.data.episodes.map( (episodeId: string, index: number) => ( {episodeId} ), )}
} variant="secondary" /> ) : ( )}
{/* Error Details */} {log.error && (
Error Details

{log.error}

)}
Content
{/* Log Content */}
{log.ingestText}
{/* Episode Facts */}
Facts
{factsLoading ? (
) : facts.length > 0 ? (
{facts.map((fact) => (

{fact.fact}

Valid: {format(new Date(fact.validAt), "dd/MM/yyyy")} {fact.invalidAt && ( Invalid:{" "} {format(new Date(fact.invalidAt), "dd/MM/yyyy")} )} {Object.keys(fact.attributes).length > 0 && ( {Object.keys(fact.attributes).length} attributes )}
))} {invalidFacts.map((fact) => (

{fact.fact}

{fact.invalidAt && ( Invalid: {new Date(fact.invalidAt).toLocaleString()} )} {Object.keys(fact.attributes).length > 0 && ( {Object.keys(fact.attributes).length} attributes )}
))}
) : (
No facts found for this episode
)}
); }