import { PrettyJsonView } from "@/src/components/ui/PrettyJsonView"; import { AnnotationQueueObjectType, type APIScoreV2, 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/trace/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/trace/lib/helpers"; import { Fragment, useMemo, useState } from "react"; import { useIsAuthenticatedAndProjectMember } from "@/src/features/auth/hooks"; import { TabsBar, TabsBarList, TabsBarTrigger, TabsBarContent, } from "@/src/components/ui/tabs-bar"; import { BreakdownTooltip } from "./BreakdownToolTip"; import { InfoIcon, PlusCircle } from "lucide-react"; import { UpsertModelFormDrawer } from "@/src/features/models/components/UpsertModelFormDrawer"; 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 { useRouter } from "next/router"; import { CopyIdsPopover } from "@/src/components/trace/CopyIdsPopover"; import { useJsonExpansion } from "@/src/components/trace/JsonExpansionContext"; export const ObservationPreview = ({ observations, projectId, scores, currentObservationId, traceId, commentCounts, viewType = "detailed", isTimeline, }: { observations: Array; projectId: string; scores: APIScoreV2[]; currentObservationId: string; traceId: string; commentCounts?: Map; viewType?: "focused" | "detailed"; isTimeline?: boolean; }) => { const [selectedTab, setSelectedTab] = useQueryParam( "view", withDefault(StringParam, "preview"), ); const [currentView, setCurrentView] = useLocalStorage<"pretty" | "json">( "jsonViewPreference", "pretty", ); const capture = usePostHogClientCapture(); const [isPrettyViewAvailable, setIsPrettyViewAvailable] = useState(false); const [emptySelectedConfigIds, setEmptySelectedConfigIds] = useLocalStorage< string[] >("emptySelectedConfigIds", []); const isAuthenticatedAndProjectMember = useIsAuthenticatedAndProjectMember(projectId); const router = useRouter(); const { peek } = router.query; const showScoresTab = isAuthenticatedAndProjectMember && peek === undefined; const { expansionState, setFieldExpansion } = useJsonExpansion(); const currentObservation = observations.find( (o) => o.id === currentObservationId, ); const observationWithInputAndOutput = api.observations.byId.useQuery({ observationId: currentObservationId, startTime: currentObservation?.startTime, traceId: traceId, projectId: projectId, }); 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 = useMemo( () => calculateDisplayTotalCost({ allObservations: observations, rootObservationId: currentObservationId, }), [observations, currentObservationId], ); if (!preloadedObservation) return
Not found
; return (
{preloadedObservation.name}
{observationWithInputAndOutput.data && ( )} {viewType === "detailed" && ( <>
{observationWithInputAndOutput.data && isGenerationLike(observationWithInputAndOutput.data.type) && ( )} )}
{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) && ( {formatTokenCounts( preloadedObservation.inputUsage, preloadedObservation.outputUsage, preloadedObservation.totalUsage, 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 }); setCurrentView(value as "pretty" | "json"); }} > Formatted JSON )} )}
setFieldExpansion("input", expansion) } onOutputExpansionChange={(expansion) => setFieldExpansion("output", expansion) } />
{preloadedObservation.statusMessage && ( )}
{observationWithInputAndOutput.data?.metadata && ( m.field === "metadata", )} currentView={currentView} externalExpansionState={expansionState.metadata} onExternalExpansionChange={(expansion) => setFieldExpansion("metadata", expansion) } /> )}
{showScoresTab && (
)}
); }; const PromptBadge = (props: { promptId: string; projectId: string }) => { const prompt = api.prompts.byId.useQuery({ id: props.promptId, projectId: props.projectId, }); if (prompt.isLoading || !prompt.data) return null; return ( Prompt: {prompt.data.name} {" - v"} {prompt.data.version} ); };