@langchain/core
Version:
Core LangChain.js abstractions and schemas
1 lines • 10.5 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\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 if (Object.keys(node.metadata ?? {}).length) {\n finalLabel += `<hr/><small><em>${Object.entries(node.metadata ?? {})\n .map(([k, v]) => `${k} = ${v}`)\n .join(\"\\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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"}