import Link from "next/link"; import { useRouter } from "next/router"; import { NumberParam, StringParam, useQueryParam, withDefault, } from "use-query-params"; import type { z } from "zod/v4"; import { OpenAiMessageView } from "@/src/components/trace2/components/IOPreview/components/ChatMessageList"; import { TabsBar, TabsBarList, TabsBarContent, TabsBarTrigger, } from "@/src/components/ui/tabs-bar"; import { Tabs, TabsList, TabsTrigger } from "@/src/components/ui/tabs"; import { Badge } from "@/src/components/ui/badge"; import { CodeView, JSONView } from "@/src/components/ui/CodeJsonViewer"; import { DetailPageNav } from "@/src/features/navigate-detail-pages/DetailPageNav"; import useProjectIdFromURL from "@/src/hooks/useProjectIdFromURL"; import { api } from "@/src/utils/api"; import { getNumberFromMap } from "@/src/utils/map-utils"; import { extractVariables, PRODUCTION_LABEL, PromptType, } from "@langfuse/shared"; import { getPromptTabs, PROMPT_TABS, } from "@/src/features/navigation/utils/prompt-tabs"; import { PromptHistoryNode } from "./prompt-history"; import { JumpToPlaygroundButton } from "@/src/features/playground/page/components/JumpToPlaygroundButton"; import { ChatMlArraySchema } from "@/src/components/schemas/ChatMlSchema"; import Generations from "@/src/components/table/use-cases/observations"; import { FlaskConical, MoreVertical, Plus } from "lucide-react"; import { useHasProjectAccess } from "@/src/features/rbac/utils/checkProjectAccess"; import { Button } from "@/src/components/ui/button"; import { usePostHogClientCapture } from "@/src/features/posthog-analytics/usePostHogClientCapture"; import { Dialog, DialogContent, DialogTrigger, } from "@/src/components/ui/dialog"; import { CreateExperimentsForm } from "@/src/features/experiments/components/CreateExperimentsForm"; import { useMemo, useState } from "react"; import { showSuccessToast } from "@/src/features/notifications/showSuccessToast"; import { DuplicatePromptButton } from "@/src/features/prompts/components/duplicate-prompt"; import Page from "@/src/components/layouts/page"; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, } from "@/src/components/ui/dropdown-menu"; import { DeletePromptVersion } from "@/src/features/prompts/components/delete-prompt-version"; import { TagPromptDetailsPopover } from "@/src/features/tag/components/TagPromptDetailsPopover"; import { SetPromptVersionLabels } from "@/src/features/prompts/components/SetPromptVersionLabels"; import { CommentDrawerButton } from "@/src/features/comments/CommentDrawerButton"; import { Command, CommandInput } from "@/src/components/ui/command"; import { renderRichPromptContent } from "@/src/features/prompts/components/prompt-content-utils"; import { PromptVariableListPreview } from "@/src/features/prompts/components/PromptVariableListPreview"; import { createBreadcrumbItems } from "@/src/features/folders/utils"; const getPythonCode = ( name: string, version: number, labels: string[], ) => `from langfuse import Langfuse # Initialize Langfuse client langfuse = Langfuse() # Get production prompt prompt = langfuse.get_prompt("${name}") # Get by label # You can use as many labels as you'd like to identify different deployment targets ${labels.length > 0 ? labels.map((label) => `prompt = langfuse.get_prompt("${name}", label="${label}")`).join("\n") : ""} # Get by version number, usually not recommended as it requires code changes to deploy new prompt versions langfuse.get_prompt("${name}", version=${version}) `; const getJsCode = ( name: string, version: number, labels: string[], ) => `import { LangfuseClient } from "@langfuse/client"; // Initialize the Langfuse client const langfuse = new LangfuseClient(); // Get production prompt const prompt = await langfuse.prompt.get("${name}"); // Get by label // You can use as many labels as you'd like to identify different deployment targets ${labels.length > 0 ? labels.map((label) => `const prompt = await langfuse.prompt.get("${name}", { label: "${label}" })`).join("\n") : ""} // Get by version number, usually not recommended as it requires code changes to deploy new prompt versions await langfuse.prompt.get("${name}", { version: ${version} }) `; export const PromptDetail = ({ promptName: promptNameProp, }: { promptName?: string } = {}) => { const projectId = useProjectIdFromURL(); const capture = usePostHogClientCapture(); const router = useRouter(); const promptName = promptNameProp || (router.query.promptName ? decodeURIComponent(router.query.promptName as string) : ""); const [currentPromptVersion, setCurrentPromptVersion] = useQueryParam( "version", NumberParam, ); const [currentPromptLabel, setCurrentPromptLabel] = useQueryParam( "label", StringParam, ); const [currentTab, setCurrentTab] = useQueryParam( "tab", withDefault(StringParam, "prompt"), ); const [isLabelPopoverOpen, setIsLabelPopoverOpen] = useState(false); const [isCreateExperimentDialogOpen, setIsCreateExperimentDialogOpen] = useState(false); const [resolutionMode, setResolutionMode] = useState<"tagged" | "resolved">( "tagged", ); const hasAccess = useHasProjectAccess({ projectId, scope: "prompts:CUD", }); const hasExperimentWriteAccess = useHasProjectAccess({ projectId, scope: "promptExperiments:CUD", }); const promptHistory = api.prompts.allVersions.useQuery( { name: promptName, projectId: projectId as string, // Typecast as query is enabled only when projectId is present }, { enabled: Boolean(projectId) }, ); const prompt = currentPromptVersion ? promptHistory.data?.promptVersions.find( (prompt) => prompt.version === currentPromptVersion, ) : currentPromptLabel ? promptHistory.data?.promptVersions.find((prompt) => prompt.labels.includes(currentPromptLabel), ) : promptHistory.data?.promptVersions[0]; const promptGraph = api.prompts.resolvePromptGraph.useQuery( { promptId: prompt?.id as string, projectId: projectId as string, }, { enabled: Boolean(projectId) && Boolean(prompt?.id), }, ); let chatMessages: z.infer | null = null; try { chatMessages = ChatMlArraySchema.parse( resolutionMode === "resolved" ? promptGraph.data?.resolvedPrompt : prompt?.prompt, ); } catch (error) { if (PromptType.Chat === prompt?.type) { console.warn( "Could not parse returned chat prompt to pretty ChatML", error, ); } } const utils = api.useUtils(); const handleExperimentSuccess = async (data?: { success: boolean; datasetId: string; runId: string; runName: string; }) => { setIsCreateExperimentDialogOpen(false); if (!data) return; void utils.datasets.baseRunDataByDatasetId.invalidate(); void utils.datasets.runsByDatasetId.invalidate(); showSuccessToast({ title: "Experiment triggered successfully", description: "Waiting for experiment to complete...", link: { text: "View experiment", href: `/project/${projectId}/datasets/${data.datasetId}/compare?runs=${data.runId}`, }, }); }; const allTags = ( api.prompts.filterOptions.useQuery( { projectId: projectId as string, }, { enabled: Boolean(projectId), trpc: { context: { skipBatch: true, }, }, refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, staleTime: Infinity, }, ).data?.tags ?? [] ).map((t) => t.value); const promptIds = useMemo( () => promptHistory.data?.promptVersions.map((p) => p.id) ?? [], [promptHistory.data?.promptVersions], ); const commentCounts = api.comments.getCountByObjectIds.useQuery( { projectId: projectId as string, objectType: "PROMPT", objectIds: promptIds, }, { enabled: Boolean(projectId) && promptIds.length > 0, trpc: { context: { skipBatch: true, }, }, refetchOnMount: false, // prevents refetching loops }, ); const { pythonCode, jsCode } = useMemo(() => { if (!prompt?.id) return { pythonCode: null, jsCode: null }; const sortedLabels = [...prompt.labels].sort((a, b) => { if (a === PRODUCTION_LABEL) return -1; if (b === PRODUCTION_LABEL) return 1; return a.localeCompare(b); }); return { pythonCode: getPythonCode(prompt.name, prompt.version, sortedLabels), jsCode: getJsCode(prompt.name, prompt.version, sortedLabels), }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [prompt?.id]); if (!promptHistory.data || !prompt) { return
Loading...
; } const extractedVariables = prompt ? extractVariables( prompt?.type === PromptType.Text ? (prompt.prompt?.toString() ?? "") : JSON.stringify(prompt.prompt), ) : []; const segments = promptName.split("/").filter((s) => s.trim()); const folderPath = segments.length > 1 ? segments.slice(0, -1).join("/") : ""; const breadcrumbItems = folderPath ? createBreadcrumbItems(folderPath) : []; return ( ({ name: item.name, href: `/project/${projectId}/prompts?folder=${encodeURIComponent(item.folderPath)}`, })), ], tabsProps: { tabs: getPromptTabs(projectId as string, promptName as string), activeTab: PROMPT_TABS.VERSIONS, }, actionButtonsLeft: ( ), actionButtonsRight: ( <> {projectId && ( )} `/project/${projectId}/prompts/${entry.id}?tab=${currentTab}` } listKey="prompts" /> ), }} >
{ setCurrentPromptVersion(version); setCurrentPromptLabel(null); }} totalCount={promptHistory.data.totalCount} commentCounts={commentCounts.data} />
e.stopPropagation()} > # {prompt.version} {prompt.commitMessage ?? prompt.name}
} promptLabels={prompt.labels} prompt={prompt} isOpen={isLabelPopoverOpen} setIsOpen={setIsLabelPopoverOpen} />
{hasAccess && ( )}
setCurrentTab(value)} > Prompt Config Linked Generations Use Prompt
{promptGraph.data?.graph && (
{ setResolutionMode(value as "tagged" | "resolved"); }} > Resolved prompt Tagged prompt
)} {prompt.type === PromptType.Chat && chatMessages ? (
) : typeof prompt.prompt === "string" ? ( resolutionMode === "resolved" && promptGraph.data?.resolvedPrompt ? ( ) : ( ) ) : ( )}
{pythonCode && } {jsCode && }

See{" "} documentation {" "} for more details on how to use prompts in frameworks such as Langchain.

); };