@langchain/core
Version:
Core LangChain.js abstractions and schemas
1 lines • 12.3 kB
Source Map (JSON)
{"version":3,"file":"graph_mermaid.cjs","names":["toBase64Url"],"sources":["../../src/runnables/graph_mermaid.ts"],"sourcesContent":["import { Edge, Node } from \"./types.js\";\nimport { toBase64Url } from \"./utils.js\";\n\nfunction _escapeNodeLabel(nodeLabel: string): string {\n // Escapes the node label for Mermaid syntax.\n return nodeLabel.replace(/[^a-zA-Z-_0-9]/g, \"_\");\n}\n\nconst MARKDOWN_SPECIAL_CHARS = [\"*\", \"_\", \"`\"];\n\nconst MERMAID_ENTITY_CODES: Record<string, string> = {\n \"#\": \"#35;\",\n \"[\": \"#91;\",\n \"]\": \"#93;\",\n \"(\": \"#40;\",\n \")\": \"#41;\",\n \"{\": \"#123;\",\n \"}\": \"#125;\",\n '\"': \"#quot;\",\n \"<\": \"#60;\",\n \">\": \"#62;\",\n \"|\": \"#124;\",\n};\n\nfunction _escapeMetadataText(text: string): string {\n return text.replace(/[#[\\](){}\"<>|]/g, (char) => MERMAID_ENTITY_CODES[char]);\n}\n\nfunction _isScalar(value: unknown): value is string | number | boolean {\n return (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n );\n}\n\nfunction _formatMetadataValue(value: unknown): string | undefined {\n if (_isScalar(value)) {\n return String(value);\n }\n if (Array.isArray(value) && value.every(_isScalar)) {\n return value.join(\",\");\n }\n return undefined;\n}\n\nfunction _generateMermaidGraphStyles(\n nodeColors: Record<string, string>\n): string {\n let styles = \"\";\n for (const [className, color] of Object.entries(nodeColors)) {\n styles += `\\tclassDef ${className} ${color};\\n`;\n }\n return styles;\n}\n\n/**\n * Draws a Mermaid graph using the provided graph data\n */\nexport function drawMermaid(\n nodes: Record<string, Node>,\n edges: Edge[],\n config?: {\n firstNode?: string;\n lastNode?: string;\n curveStyle?: string;\n withStyles?: boolean;\n nodeColors?: Record<string, string>;\n wrapLabelNWords?: number;\n }\n): string {\n const {\n firstNode,\n lastNode,\n nodeColors,\n withStyles = true,\n curveStyle = \"linear\",\n wrapLabelNWords = 9,\n } = config ?? {};\n // Initialize Mermaid graph configuration\n let mermaidGraph = withStyles\n ? `%%{init: {'flowchart': {'curve': '${curveStyle}'}}}%%\\ngraph TD;\\n`\n : \"graph TD;\\n\";\n if (withStyles) {\n // Node formatting templates\n const defaultClassLabel = \"default\";\n const formatDict: Record<string, string> = {\n [defaultClassLabel]: \"{0}({1})\",\n };\n if (firstNode !== undefined) {\n formatDict[firstNode] = \"{0}([{1}]):::first\";\n }\n if (lastNode !== undefined) {\n formatDict[lastNode] = \"{0}([{1}]):::last\";\n }\n\n // Add nodes to the graph\n for (const [key, node] of Object.entries(nodes)) {\n const nodeName = node.name.split(\":\").pop() ?? \"\";\n const label = MARKDOWN_SPECIAL_CHARS.some(\n (char) => nodeName.startsWith(char) && nodeName.endsWith(char)\n )\n ? `<p>${nodeName}</p>`\n : nodeName;\n\n let finalLabel = label;\n const metadataLines = Object.entries(node.metadata ?? {}).flatMap(\n ([k, v]) => {\n const value = _formatMetadataValue(v);\n return value === undefined\n ? []\n : [_escapeMetadataText(`${k} = ${value}`)];\n }\n );\n if (metadataLines.length) {\n finalLabel += `<hr/><small><em>${metadataLines.join(\n \"\\n\"\n )}</em></small>`;\n }\n\n const nodeLabel = (formatDict[key] ?? formatDict[defaultClassLabel])\n .replace(\"{0}\", _escapeNodeLabel(key))\n .replace(\"{1}\", finalLabel);\n\n mermaidGraph += `\\t${nodeLabel}\\n`;\n }\n }\n\n // Group edges by their common prefixes\n const edgeGroups: Record<string, Edge[]> = {};\n for (const edge of edges) {\n const srcParts = edge.source.split(\":\");\n const tgtParts = edge.target.split(\":\");\n const commonPrefix = srcParts\n .filter((src, i) => src === tgtParts[i])\n .join(\":\");\n if (!edgeGroups[commonPrefix]) {\n edgeGroups[commonPrefix] = [];\n }\n edgeGroups[commonPrefix].push(edge);\n }\n\n const seenSubgraphs = new Set<string>();\n\n // sort prefixes by path length for correct nesting\n function sortPrefixesByDepth(prefixes: string[]): string[] {\n return [...prefixes].sort((a, b) => {\n return a.split(\":\").length - b.split(\":\").length;\n });\n }\n\n function addSubgraph(edges: Edge[], prefix: string): void {\n const selfLoop = edges.length === 1 && edges[0].source === edges[0].target;\n if (prefix && !selfLoop) {\n const subgraph = prefix.split(\":\").pop()!;\n\n if (seenSubgraphs.has(prefix)) {\n throw new Error(\n `Found duplicate subgraph '${subgraph}' at '${prefix} -- this likely means that ` +\n \"you're reusing a subgraph node with the same name. \" +\n \"Please adjust your graph to have subgraph nodes with unique names.\"\n );\n }\n\n seenSubgraphs.add(prefix);\n mermaidGraph += `\\tsubgraph ${subgraph}\\n`;\n }\n\n // all nested prefixes for this level, sorted by depth\n const nestedPrefixes = sortPrefixesByDepth(\n Object.keys(edgeGroups).filter(\n (nestedPrefix) =>\n nestedPrefix.startsWith(`${prefix}:`) &&\n nestedPrefix !== prefix &&\n nestedPrefix.split(\":\").length === prefix.split(\":\").length + 1\n )\n );\n\n for (const nestedPrefix of nestedPrefixes) {\n addSubgraph(edgeGroups[nestedPrefix], nestedPrefix);\n }\n\n for (const edge of edges) {\n const { source, target, data, conditional } = edge;\n\n let edgeLabel = \"\";\n if (data !== undefined) {\n let edgeData = data;\n const words = edgeData.split(\" \");\n if (words.length > wrapLabelNWords) {\n edgeData = Array.from(\n { length: Math.ceil(words.length / wrapLabelNWords) },\n (_, i) =>\n words\n .slice(i * wrapLabelNWords, (i + 1) * wrapLabelNWords)\n .join(\" \")\n ).join(\" <br> \");\n }\n edgeLabel = conditional\n ? ` -. ${edgeData} .-> `\n : ` -- ${edgeData} --> `;\n } else {\n edgeLabel = conditional ? \" -.-> \" : \" --> \";\n }\n\n mermaidGraph += `\\t${_escapeNodeLabel(\n source\n )}${edgeLabel}${_escapeNodeLabel(target)};\\n`;\n }\n\n if (prefix && !selfLoop) {\n mermaidGraph += \"\\tend\\n\";\n }\n }\n\n // Start with the top-level edges (no common prefix)\n addSubgraph(edgeGroups[\"\"] ?? [], \"\");\n\n // Add remaining top-level subgraphs\n for (const prefix in edgeGroups) {\n if (!prefix.includes(\":\") && prefix !== \"\") {\n addSubgraph(edgeGroups[prefix], prefix);\n }\n }\n\n // Add custom styles for nodes\n if (withStyles) {\n mermaidGraph += _generateMermaidGraphStyles(nodeColors ?? {});\n }\n\n return mermaidGraph;\n}\n\n/**\n * Renders Mermaid graph using the Mermaid.INK API.\n *\n * @example\n * ```javascript\n * const image = await drawMermaidImage(mermaidSyntax, {\n * backgroundColor: \"white\",\n * imageType: \"png\",\n * });\n * fs.writeFileSync(\"image.png\", image);\n * ```\n *\n * @param mermaidSyntax - The Mermaid syntax to render.\n * @param config - The configuration for the image.\n * @returns The image as a Blob.\n */\nexport async function drawMermaidImage(\n mermaidSyntax: string,\n config?: {\n /**\n * The type of image to render.\n * @default \"png\"\n */\n imageType?: \"png\" | \"jpeg\" | \"webp\";\n backgroundColor?: string;\n }\n) {\n let backgroundColor = config?.backgroundColor ?? \"white\";\n const imageType = config?.imageType ?? \"png\";\n\n const mermaidSyntaxEncoded = toBase64Url(mermaidSyntax);\n\n // Check if the background color is a hexadecimal color code using regex\n if (backgroundColor !== undefined) {\n const hexColorPattern = /^#(?:[0-9a-fA-F]{3}){1,2}$/;\n if (!hexColorPattern.test(backgroundColor)) {\n backgroundColor = `!${backgroundColor}`;\n }\n }\n const imageUrl = `https://mermaid.ink/img/${mermaidSyntaxEncoded}?bgColor=${backgroundColor}&type=${imageType}`;\n const res = await fetch(imageUrl);\n if (!res.ok) {\n throw new Error(\n [\n `Failed to render the graph using the Mermaid.INK API.`,\n `Status code: ${res.status}`,\n `Status text: ${res.statusText}`,\n ].join(\"\\n\")\n );\n }\n const content = await res.blob();\n return content;\n}\n"],"mappings":"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"}