/** * TraceDataContext - Provides read-only trace data and derived structures. * * Purpose: * - Provides trace, observations, scores from props * - Computes and memoizes tree structure, nodeMap, and searchItems * * Not responsible for: * - Data fetching (done by parent via API hooks) * - UI state (selection, collapsed nodes) - see SelectionContext * - Display preferences - see ViewPreferencesContext */ import { createContext, useContext, useMemo, type ReactNode } from "react"; import { type TraceDomain, type ScoreDomain } from "@langfuse/shared"; import { type ObservationReturnTypeWithMetadata } from "@/src/server/api/routers/traces"; import { type WithStringifiedMetadata } from "@/src/utils/clientSideDomainTypes"; import { type TreeNode, type TraceSearchListItem } from "../lib/types"; import { buildTraceUiData } from "../lib/tree-building"; import { useViewPreferences } from "./ViewPreferencesContext"; import { useMergedScores } from "@/src/features/scores/lib/useMergedScores"; type TraceType = Omit< WithStringifiedMetadata, "input" | "output" > & { input: string | null; output: string | null; }; interface TraceDataContextValue { trace: TraceType; observations: ObservationReturnTypeWithMetadata[]; serverScores: WithStringifiedMetadata[]; mergedScores: WithStringifiedMetadata[]; corrections: ScoreDomain[]; roots: TreeNode[]; nodeMap: Map; searchItems: TraceSearchListItem[]; hiddenObservationsCount: number; comments: Map; } const TraceDataContext = createContext(null); export function useTraceData(): TraceDataContextValue { const context = useContext(TraceDataContext); if (!context) { throw new Error("useTraceData must be used within a TraceDataProvider"); } return context; } interface TraceDataProviderProps { trace: TraceType; observations: ObservationReturnTypeWithMetadata[]; serverScores: WithStringifiedMetadata[]; corrections: ScoreDomain[]; comments: Map; children: ReactNode; } /** * TraceDataProvider must be rendered within ViewPreferencesProvider. * It consumes minObservationLevel directly from ViewPreferencesContext. */ export function TraceDataProvider({ trace, observations, serverScores, corrections, comments, children, }: TraceDataProviderProps) { const { minObservationLevel } = useViewPreferences(); const uiData = useMemo(() => { return buildTraceUiData(trace, observations, minObservationLevel); }, [trace, observations, minObservationLevel]); // Merge scores with optimistic cache const mergedScores = useMergedScores( serverScores, { type: "trace", traceId: trace.id, }, "target-and-child-scores", ); const value = useMemo( () => ({ trace, observations, serverScores: serverScores, mergedScores, corrections, roots: uiData.roots, nodeMap: uiData.nodeMap, searchItems: uiData.searchItems, hiddenObservationsCount: uiData.hiddenObservationsCount, comments, }), [ trace, observations, serverScores, mergedScores, corrections, uiData, comments, ], ); return ( {children} ); }