import { PrettyJsonView } from "@/src/components/ui/PrettyJsonView"; import { type ScoreDomain, type TraceDomain, AnnotationQueueObjectType, isGenerationLike, } from "@langfuse/shared"; import { AggUsageBadge } from "@/src/components/token-usage-badge"; import { Badge } from "@/src/components/ui/badge"; import { type ObservationReturnTypeWithMetadata } from "@/src/server/api/routers/traces"; import { IOPreview } from "@/src/components/trace2/components/IOPreview/IOPreview"; import { formatIntervalSeconds } from "@/src/utils/dates"; import { withDefault, StringParam, useQueryParam } from "use-query-params"; import ScoresTable from "@/src/components/table/use-cases/scores"; import { AnnotateDrawer } from "@/src/features/scores/components/AnnotateDrawer"; import useLocalStorage from "@/src/components/useLocalStorage"; import { CommentDrawerButton } from "@/src/features/comments/CommentDrawerButton"; import { api } from "@/src/utils/api"; import { NewDatasetItemFromExistingObject } from "@/src/features/datasets/components/NewDatasetItemFromExistingObject"; import { CreateNewAnnotationQueueItem } from "@/src/features/annotation-queues/components/CreateNewAnnotationQueueItem"; import { useMemo, useState, useEffect } from "react"; import { usdFormatter } from "@/src/utils/numbers"; import { useIsAuthenticatedAndProjectMember } from "@/src/features/auth/hooks"; import type Decimal from "decimal.js"; import { TabsBar, TabsBarContent, TabsBarList, TabsBarTrigger, } from "@/src/components/ui/tabs-bar"; import { BreakdownTooltip } from "@/src/components/trace2/components/_shared/BreakdownToolTip"; import { ExternalLinkIcon, InfoIcon } from "lucide-react"; import { LocalIsoDate } from "@/src/components/LocalIsoDate"; import { ItemBadge } from "@/src/components/ItemBadge"; import Link from "next/link"; import { Tabs, TabsList, TabsTrigger } from "@/src/components/ui/tabs"; import { Switch } from "@/src/components/ui/switch"; import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture"; import { useRouter } from "next/router"; import { CopyIdsPopover } from "@/src/components/trace2/components/_shared/CopyIdsPopover"; import { useJsonExpansion } from "@/src/components/trace2/contexts/JsonExpansionContext"; import { TraceLogView } from "@/src/components/trace2/components/TraceLogView/TraceLogView"; import { useParsedTrace } from "@/src/hooks/useParsedTrace"; import { useJsonBetaToggle } from "@/src/components/trace2/hooks/useJsonBetaToggle"; import { TraceDataProvider } from "@/src/components/trace2/contexts/TraceDataContext"; import { ViewPreferencesProvider } from "@/src/components/trace2/contexts/ViewPreferencesContext"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/src/components/ui/tooltip"; import { getMostRecentCorrection } from "@/src/features/corrections/utils/getMostRecentCorrection"; import TagList from "@/src/features/tag/components/TagList"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/src/components/ui/alert-dialog"; import { type WithStringifiedMetadata } from "@/src/utils/clientSideDomainTypes"; const LOG_VIEW_CONFIRMATION_THRESHOLD = 150; const LOG_VIEW_DISABLED_THRESHOLD = 350; export const TracePreview = ({ trace, observations, serverScores: scores, corrections, commentCounts, viewType = "detailed", showCommentButton = false, precomputedCost, }: { trace: Omit, "input" | "output"> & { latency?: number; input: string | null; output: string | null; }; observations: ObservationReturnTypeWithMetadata[]; serverScores: WithStringifiedMetadata[]; corrections: ScoreDomain[]; commentCounts?: Map; viewType?: "detailed" | "focused"; showCommentButton?: boolean; precomputedCost: Decimal | undefined; }) => { const [selectedTab, setSelectedTab] = useQueryParam( "view", withDefault(StringParam, "preview"), ); const [currentView, setCurrentView] = useLocalStorage< "pretty" | "json" | "json-beta" >("jsonViewPreference", "pretty"); const { jsonBetaEnabled, selectedViewTab, handleViewTabChange, handleBetaToggle, } = useJsonBetaToggle(currentView, setCurrentView); const [isPrettyViewAvailable, setIsPrettyViewAvailable] = useState(false); const isAuthenticatedAndProjectMember = useIsAuthenticatedAndProjectMember( trace.projectId, ); const capture = usePostHogClientCapture(); const router = useRouter(); const { peek } = router.query; const showScoresTab = isAuthenticatedAndProjectMember && peek === undefined; const { formattedExpansion, setFormattedFieldExpansion, jsonExpansion, setJsonFieldExpansion, advancedJsonExpansion, setAdvancedJsonExpansion, } = useJsonExpansion(); const traceMedia = api.media.getByTraceOrObservationId.useQuery( { traceId: trace.id, projectId: trace.projectId, }, { enabled: isAuthenticatedAndProjectMember, refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false, staleTime: 50 * 60 * 1000, // 50 minutes }, ); const traceCorrections = corrections.filter( (c) => c.traceId === trace.id && c.observationId === null, ); const outputCorrection = getMostRecentCorrection(traceCorrections); // Parse trace I/O in background (Web Worker) const { parsedInput, parsedOutput, parsedMetadata, isParsing } = useParsedTrace({ traceId: trace.id, input: trace.input, output: trace.output, metadata: trace.metadata, }); const totalCost = precomputedCost; const usageDetails = useMemo( () => observations .filter((o) => isGenerationLike(o.type)) .map((o) => o.usageDetails), [observations], ); // For performance reasons, we preemptively disable the log view if there are too many observations. const isLogViewDisabled = observations.length > LOG_VIEW_DISABLED_THRESHOLD; const requiresConfirmation = observations.length > LOG_VIEW_CONFIRMATION_THRESHOLD && !isLogViewDisabled; const showLogViewTab = observations.length > 0; const [hasLogViewConfirmed, setHasLogViewConfirmed] = useState(false); const [showLogViewDialog, setShowLogViewDialog] = useState(false); useEffect(() => { setHasLogViewConfirmed(false); }, [trace.id]); useEffect(() => { if ((isLogViewDisabled || !showLogViewTab) && selectedTab === "log") { setSelectedTab("preview"); } }, [isLogViewDisabled, showLogViewTab, selectedTab, setSelectedTab]); const handleConfirmLogView = () => { setHasLogViewConfirmed(true); setShowLogViewDialog(false); setSelectedTab("log"); }; return (
{trace.name}
{viewType === "detailed" && ( <>
)} {viewType === "focused" && showCommentButton && ( )}
{trace.sessionId ? ( Session: {trace.sessionId} ) : null} {trace.userId ? ( User ID: {trace.userId} ) : null} {trace.environment ? ( Env: {trace.environment} ) : null} {viewType === "detailed" && ( <> {!!trace.latency && ( Latency: {formatIntervalSeconds(trace.latency)} )} {totalCost && ( isGenerationLike(o.type)) .map((o) => o.costDetails)} isCost > Total Cost: {usdFormatter(totalCost.toNumber())} )} {usageDetails.length > 0 && ( } variant="tertiary" /> )} {!!trace.release && ( Release: {trace.release} )} {!!trace.version && ( Version: {trace.version} )} )}
{ // on tab click, is confirmation is needed? if ( value === "log" && requiresConfirmation && !hasLogViewConfirmed ) { setShowLogViewDialog(true); return; } capture("trace_detail:view_mode_switch", { mode: value }); setSelectedTab(value); }} > {viewType === "detailed" && ( Preview {showLogViewTab && ( Log View {isLogViewDisabled ? `Log View is disabled for traces with more than ${LOG_VIEW_DISABLED_THRESHOLD} observations (this trace has ${observations.length})` : requiresConfirmation ? `Log View may be slow with ${observations.length} observations. Click to confirm.` : "Shows all observations concatenated. Great for quickly scanning through them. Nullish values are omitted."} )} {showScoresTab && ( Scores )} {selectedTab.includes("preview") && isPrettyViewAvailable && ( <> { capture("trace_detail:io_mode_switch", { view: value }); handleViewTabChange(value); }} > Formatted JSON {selectedViewTab === "json" && (
Beta
)} )} {selectedTab === "log" && ( <> Formatted JSON {selectedViewTab === "json" && (
Beta
)} )}
)} {/* show preview always if not detailed view */}
setFormattedFieldExpansion( "input", expansion as Record, ) } onOutputExpansionChange={(expansion) => setFormattedFieldExpansion( "output", expansion as Record, ) } jsonInputExpanded={jsonExpansion.input} jsonOutputExpanded={jsonExpansion.output} onJsonInputExpandedChange={(expanded) => setJsonFieldExpansion("input", expanded) } onJsonOutputExpandedChange={(expanded) => setJsonFieldExpansion("output", expanded) } advancedJsonExpansionState={advancedJsonExpansion} onAdvancedJsonExpansionChange={setAdvancedJsonExpansion} projectId={trace.projectId} traceId={trace.id} environment={trace.environment} /> {trace.tags.length > 0 && ( <>
{"Tags"}
)}
m.field === "metadata") ?? [] } currentView={ currentView === "json-beta" ? "pretty" : currentView } externalExpansionState={formattedExpansion.metadata} onExternalExpansionChange={(expansion) => setFormattedFieldExpansion( "metadata", expansion as Record, ) } />
{showScoresTab && (
)}
{/* Confirmation dialog for log view with many observations */} Sluggish Performance Warning This trace has {observations.length} observations. The log view may be slow to load and interact with. Do you want to continue? setShowLogViewDialog(false)}> Cancel Show Log View
); };