import { useState, useEffect, type ReactNode } from "react"; import { useFetcher } from "@remix-run/react"; import { AlertCircle, File, Loader2, MessageSquare } 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"; import { SpaceDropdown } from "../spaces/space-dropdown"; import { StyledMarkdown } from "../common/styled-markdown"; 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 formatString("IN QUEUE"); } return formatString(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 (log.data?.type === "DOCUMENT" && log.data?.episodes?.length > 0) { setFactsLoading(true); setFacts([]); // 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`); } else { setFacts([]); setInvalidFacts([]); } }, [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 (
) : ( ) } variant="secondary" /> {/* Space Assignment for CONVERSATION type */} {log.data.type.toLowerCase() === "conversation" && log?.episodeUUID && (
Spaces
)}
{/* Error Details */} {log.error && (

{log.error}

)} {log.data?.type === "CONVERSATION" && (
{/* Log Content */}
{log.ingestText}
)} {/* Episodes List for DOCUMENT type */} {log.data?.type === "DOCUMENT" && log.episodeDetails?.length > 0 && (
Episodes ({log.episodeDetails.length})
{log.episodeDetails.map((episode: any, index: number) => (
Episode {index + 1} {episode.uuid}
{/* Episode Content */}
Content
{episode.content}
))}
)} {/* 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
)}
); }