import React from "react"; import { cn } from "@/src/utils/tailwind"; import { MUSTACHE_REGEX, isValidVariableName, PromptDependencyRegex, type ParsedPromptDependencyTag, } from "@langfuse/shared"; import { Badge } from "@/src/components/ui/badge"; import { Button } from "@/src/components/ui/button"; import { FileCode } from "lucide-react"; const PromptVar = ({ name, isValid }: { name: string; isValid: boolean }) => ( {`{{${name}}}`} ); type PromptReference = ParsedPromptDependencyTag & { position: number; }; const PromptReference = ({ promptRef, projectId, }: { promptRef: PromptReference; projectId: string; }) => { const getPromptUrl = (projectId: string, tag: PromptReference) => { const baseUrl = `/project/${projectId}/prompts/`; if (tag.type === "version") { return `${baseUrl}${encodeURIComponent(tag.name)}?version=${tag.version}`; } else { return `${baseUrl}${encodeURIComponent(tag.name)}?label=${encodeURIComponent(tag.label)}`; } }; return ( ); }; // Higher-level function that renders prompt content with all the rich formatting export const renderRichPromptContent = ( projectId: string, content: string, ): React.ReactNode[] => { if (!content) return []; const createTextNode = (text: string, key: string) => ( {text} ); const parts: React.ReactNode[] = []; let lastIndex = 0; // Create combined regex that captures both patterns in a single pass // Group 1: prompt dependency inner content, Group 2: variable name const combinedRegex = new RegExp( `${PromptDependencyRegex.source}|${MUSTACHE_REGEX.source}`, "g", ); let match; while ((match = combinedRegex.exec(content)) !== null) { const index = match.index ?? 0; const fullMatch = match[0]; // Add any text before this match if (index > lastIndex) { const textBefore = content.substring(lastIndex, index); if (textBefore) { parts.push(createTextNode(textBefore, `text-${lastIndex}-${index}`)); } } // Determine type based on which capture group matched and process directly if (match[1] !== undefined) { // First capture group = prompt dependency const innerContent = match[1]; const tagParts = innerContent.split("|"); const params: Record = {}; tagParts.forEach((part) => { const [key, value] = part.split("="); if (key && value) { params[key] = value; } }); if (params.name) { const tag: PromptReference = params.version ? { name: params.name, type: "version", version: Number(params.version), position: index, } : { name: params.name, type: "label", label: params.label || "", position: index, }; parts.push( , ); } else { parts.push(createTextNode(fullMatch, `raw-${index}`)); } } else if (match[2] !== undefined) { // Second capture group = variable const variable = match[2]; const isValid = isValidVariableName(variable); parts.push( , ); } lastIndex = index + fullMatch.length; } // Add any remaining text if (lastIndex < content.length) { const remainingText = content.substring(lastIndex); if (remainingText) { parts.push(createTextNode(remainingText, `text-${lastIndex}-end`)); } } return parts; };