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.data?.type === "CONVERSATION" && (
)}
{/* 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
)}
);
}