import { PrettyJsonView } from "@/src/components/ui/PrettyJsonView"; import { AnnotationQueueObjectType, type ScoreDomain, isGenerationLike, } from "@langfuse/shared"; import { Badge } from "@/src/components/ui/badge"; import { type ObservationReturnType } from "@/src/server/api/routers/traces"; import { api } from "@/src/utils/api"; import { IOPreview } from "@/src/components/trace2/components/IOPreview/IOPreview"; import { formatIntervalSeconds } from "@/src/utils/dates"; import Link from "next/link"; import { usdFormatter, formatTokenCounts } from "@/src/utils/numbers"; import { withDefault, StringParam, useQueryParam } from "use-query-params"; import ScoresTable from "@/src/components/table/use-cases/scores"; import { JumpToPlaygroundButton } from "@/src/features/playground/page/components/JumpToPlaygroundButton"; import { AnnotateDrawer } from "@/src/features/scores/components/AnnotateDrawer"; import useLocalStorage from "@/src/components/useLocalStorage"; import { CommentDrawerButton } from "@/src/features/comments/CommentDrawerButton"; import { cn } from "@/src/utils/tailwind"; import { NewDatasetItemFromExistingObject } from "@/src/features/datasets/components/NewDatasetItemFromExistingObject"; import { CreateNewAnnotationQueueItem } from "@/src/features/annotation-queues/components/CreateNewAnnotationQueueItem"; import { calculateDisplayTotalCost } from "@/src/components/trace2/lib/helpers"; import { Fragment, useState } from "react"; import type Decimal from "decimal.js"; import { useIsAuthenticatedAndProjectMember } from "@/src/features/auth/hooks"; import { TabsBar, TabsBarList, TabsBarTrigger, TabsBarContent, } from "@/src/components/ui/tabs-bar"; import { BreakdownTooltip, calculateAggregatedUsage, } from "@/src/components/trace2/components/_shared/BreakdownToolTip"; import { ExternalLinkIcon, InfoIcon, PlusCircle } from "lucide-react"; import { UpsertModelFormDialog } from "@/src/features/models/components/UpsertModelFormDialog"; import { LocalIsoDate } from "@/src/components/LocalIsoDate"; import { ItemBadge } from "@/src/components/ItemBadge"; import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture"; import { Tabs, TabsList, TabsTrigger } from "@/src/components/ui/tabs"; import { Switch } from "@/src/components/ui/switch"; import { useRouter } from "next/router"; import { CopyIdsPopover } from "@/src/components/trace2/components/_shared/CopyIdsPopover"; import { useJsonExpansion } from "@/src/components/trace2/contexts/JsonExpansionContext"; import { type WithStringifiedMetadata } from "@/src/utils/clientSideDomainTypes"; import { useParsedObservation } from "@/src/hooks/useParsedObservation"; import { PromptBadge } from "@/src/components/trace2/components/_shared/PromptBadge"; import { useJsonBetaToggle } from "@/src/components/trace2/hooks/useJsonBetaToggle"; import { getMostRecentCorrection } from "@/src/features/corrections/utils/getMostRecentCorrection"; export const ObservationPreview = ({ observations, projectId, serverScores: scores, corrections, currentObservationId, traceId, commentCounts, viewType = "detailed", isTimeline, showCommentButton = false, precomputedCost, }: { observations: Array; projectId: string; serverScores: WithStringifiedMetadata[]; corrections: ScoreDomain[]; currentObservationId: string; traceId: string; commentCounts?: Map; viewType?: "focused" | "detailed"; isTimeline?: boolean; 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 capture = usePostHogClientCapture(); const [isPrettyViewAvailable, setIsPrettyViewAvailable] = useState(false); const isAuthenticatedAndProjectMember = useIsAuthenticatedAndProjectMember(projectId); const router = useRouter(); const { peek } = router.query; const showScoresTab = isAuthenticatedAndProjectMember && peek === undefined; const { formattedExpansion, setFormattedFieldExpansion, jsonExpansion, setJsonFieldExpansion, advancedJsonExpansion, setAdvancedJsonExpansion, } = useJsonExpansion(); const currentObservation = observations.find( (o) => o.id === currentObservationId, ); const currentObservationScores = scores.filter( (s) => s.observationId === currentObservationId, ); const currentObservationCorrections = corrections.filter( (c) => c.observationId === currentObservationId, ); // Fetch and parse observation input/output in background (Web Worker) const { observation: observationWithIORaw, parsedInput, parsedOutput, parsedMetadata, isLoadingObservation, isWaitingForParsing, } = useParsedObservation({ observationId: currentObservationId, traceId: traceId, projectId: projectId, startTime: currentObservation?.startTime, baseObservation: currentObservation, }); // Type narrowing: when baseObservation is provided, result has full observation fields // (EventBatchIOOutput case only occurs when baseObservation is missing) const observationWithIO = observationWithIORaw && "type" in observationWithIORaw ? observationWithIORaw : undefined; const observationMedia = api.media.getByTraceOrObservationId.useQuery( { traceId: traceId, observationId: currentObservationId, projectId: projectId, }, { enabled: isAuthenticatedAndProjectMember, refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false, staleTime: 50 * 60 * 1000, // 50 minutes }, ); const preloadedObservation = observations.find( (o) => o.id === currentObservationId, ); const thisCost = preloadedObservation ? calculateDisplayTotalCost({ allObservations: [preloadedObservation], }) : undefined; const totalCost = precomputedCost; if (!preloadedObservation) return
Not found
; return (
{preloadedObservation.name}
{observationWithIO && ( )} {viewType === "detailed" && ( <>
{observationWithIO && isGenerationLike(observationWithIO.type) && ( )} )} {viewType === "focused" && showCommentButton && ( )}
{viewType === "detailed" && ( {preloadedObservation.endTime ? ( Latency:{" "} {formatIntervalSeconds( (preloadedObservation.endTime.getTime() - preloadedObservation.startTime.getTime()) / 1000, )} ) : null} {preloadedObservation.timeToFirstToken ? ( Time to first token:{" "} {formatIntervalSeconds( preloadedObservation.timeToFirstToken, )} ) : null} {preloadedObservation.environment ? ( Env: {preloadedObservation.environment} ) : null} {thisCost ? ( {usdFormatter(thisCost.toNumber())} ) : undefined} {totalCost && (!thisCost || !totalCost.equals(thisCost)) ? ( ∑ {usdFormatter(totalCost.toNumber())} ) : undefined} {preloadedObservation.promptId ? ( ) : undefined} {isGenerationLike(preloadedObservation.type) && (() => { const aggregatedUsage = calculateAggregatedUsage( preloadedObservation.usageDetails, ); return ( {formatTokenCounts( aggregatedUsage.input, aggregatedUsage.output, aggregatedUsage.total, true, )} ); })()} {preloadedObservation.version ? ( Version: {preloadedObservation.version} ) : undefined} {preloadedObservation.model ? ( preloadedObservation.internalModelId ? ( {preloadedObservation.model} ) : ( 0 ? Object.keys(preloadedObservation.usageDetails) .filter((key) => key != "total") .reduce( (acc, key) => { acc[key] = 0.000001; return acc; }, {} as Record, ) : undefined, }} className="cursor-pointer" > {preloadedObservation.model} ) ) : null} {preloadedObservation.modelParameters && typeof preloadedObservation.modelParameters === "object" ? Object.entries(preloadedObservation.modelParameters) .filter(([_, value]) => value !== null) .map(([key, value]) => { const valueString = Object.prototype.toString.call(value) === "[object Object]" ? JSON.stringify(value) : value?.toString(); return ( {/* CHILD: This span handles the text truncation */} {key}: {valueString} ); }) : null} )}
setSelectedTab(value)} > {viewType === "detailed" && ( Preview {showScoresTab && ( Scores )} {selectedTab.includes("preview") && isPrettyViewAvailable && ( <> { capture("trace_detail:io_mode_switch", { view: value }); handleViewTabChange(value); }} > Formatted JSON {selectedViewTab === "json" && (
Beta
)} )}
)}
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={projectId} traceId={traceId} environment={preloadedObservation.environment} />
{preloadedObservation.statusMessage && ( )}
{observationWithIO?.metadata && ( m.field === "metadata", )} currentView={ currentView === "json-beta" ? "pretty" : currentView } externalExpansionState={formattedExpansion.metadata} onExternalExpansionChange={(expansion) => setFormattedFieldExpansion( "metadata", expansion as Record, ) } /> )}
{showScoresTab && (
)}
); };