@langchain/core
Version:
Core LangChain.js abstractions and schemas
110 lines (108 loc) • 4.99 kB
JavaScript
//#region src/runnables/graph_mermaid.ts
function _escapeNodeLabel(nodeLabel) {
return nodeLabel.replace(/[^a-zA-Z-_0-9]/g, "_");
}
const MARKDOWN_SPECIAL_CHARS = [
"*",
"_",
"`"
];
function _generateMermaidGraphStyles(nodeColors) {
let styles = "";
for (const [className, color] of Object.entries(nodeColors)) styles += `\tclassDef ${className} ${color};\n`;
return styles;
}
/**
* Draws a Mermaid graph using the provided graph data
*/
function drawMermaid(nodes, edges, config) {
const { firstNode, lastNode, nodeColors, withStyles = true, curveStyle = "linear", wrapLabelNWords = 9 } = config ?? {};
let mermaidGraph = withStyles ? `%%{init: {'flowchart': {'curve': '${curveStyle}'}}}%%\ngraph TD;\n` : "graph TD;\n";
if (withStyles) {
const defaultClassLabel = "default";
const formatDict = { [defaultClassLabel]: "{0}({1})" };
if (firstNode !== void 0) formatDict[firstNode] = "{0}([{1}]):::first";
if (lastNode !== void 0) formatDict[lastNode] = "{0}([{1}]):::last";
for (const [key, node] of Object.entries(nodes)) {
const nodeName = node.name.split(":").pop() ?? "";
const label = MARKDOWN_SPECIAL_CHARS.some((char) => nodeName.startsWith(char) && nodeName.endsWith(char)) ? `<p>${nodeName}</p>` : nodeName;
let finalLabel = label;
if (Object.keys(node.metadata ?? {}).length) finalLabel += `<hr/><small><em>${Object.entries(node.metadata ?? {}).map(([k, v]) => `${k} = ${v}`).join("\n")}</em></small>`;
const nodeLabel = (formatDict[key] ?? formatDict[defaultClassLabel]).replace("{0}", _escapeNodeLabel(key)).replace("{1}", finalLabel);
mermaidGraph += `\t${nodeLabel}\n`;
}
}
const edgeGroups = {};
for (const edge of edges) {
const srcParts = edge.source.split(":");
const tgtParts = edge.target.split(":");
const commonPrefix = srcParts.filter((src, i) => src === tgtParts[i]).join(":");
if (!edgeGroups[commonPrefix]) edgeGroups[commonPrefix] = [];
edgeGroups[commonPrefix].push(edge);
}
const seenSubgraphs = /* @__PURE__ */ new Set();
function addSubgraph(edges$1, prefix) {
const selfLoop = edges$1.length === 1 && edges$1[0].source === edges$1[0].target;
if (prefix && !selfLoop) {
const subgraph = prefix.split(":").pop();
if (seenSubgraphs.has(subgraph)) throw new Error(`Found duplicate subgraph '${subgraph}' -- this likely means that you're reusing a subgraph node with the same name. Please adjust your graph to have subgraph nodes with unique names.`);
seenSubgraphs.add(subgraph);
mermaidGraph += `\tsubgraph ${subgraph}\n`;
}
for (const edge of edges$1) {
const { source, target, data, conditional } = edge;
let edgeLabel = "";
if (data !== void 0) {
let edgeData = data;
const words = edgeData.split(" ");
if (words.length > wrapLabelNWords) edgeData = Array.from({ length: Math.ceil(words.length / wrapLabelNWords) }, (_, i) => words.slice(i * wrapLabelNWords, (i + 1) * wrapLabelNWords).join(" ")).join(" <br> ");
edgeLabel = conditional ? ` -. ${edgeData} .-> ` : ` -- ${edgeData} --> `;
} else edgeLabel = conditional ? " -.-> " : " --> ";
mermaidGraph += `\t${_escapeNodeLabel(source)}${edgeLabel}${_escapeNodeLabel(target)};\n`;
}
for (const nestedPrefix in edgeGroups) if (nestedPrefix.startsWith(`${prefix}:`) && nestedPrefix !== prefix) addSubgraph(edgeGroups[nestedPrefix], nestedPrefix);
if (prefix && !selfLoop) mermaidGraph += " end\n";
}
addSubgraph(edgeGroups[""] ?? [], "");
for (const prefix in edgeGroups) if (!prefix.includes(":") && prefix !== "") addSubgraph(edgeGroups[prefix], prefix);
if (withStyles) mermaidGraph += _generateMermaidGraphStyles(nodeColors ?? {});
return mermaidGraph;
}
/**
* Renders Mermaid graph using the Mermaid.INK API.
*
* @example
* ```javascript
* const image = await drawMermaidImage(mermaidSyntax, {
* backgroundColor: "white",
* imageType: "png",
* });
* fs.writeFileSync("image.png", image);
* ```
*
* @param mermaidSyntax - The Mermaid syntax to render.
* @param config - The configuration for the image.
* @returns The image as a Blob.
*/
async function drawMermaidImage(mermaidSyntax, config) {
let backgroundColor = config?.backgroundColor ?? "white";
const imageType = config?.imageType ?? "png";
const mermaidSyntaxEncoded = btoa(mermaidSyntax);
if (backgroundColor !== void 0) {
const hexColorPattern = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
if (!hexColorPattern.test(backgroundColor)) backgroundColor = `!${backgroundColor}`;
}
const imageUrl = `https://mermaid.ink/img/${mermaidSyntaxEncoded}?bgColor=${backgroundColor}&type=${imageType}`;
const res = await fetch(imageUrl);
if (!res.ok) throw new Error([
`Failed to render the graph using the Mermaid.INK API.`,
`Status code: ${res.status}`,
`Status text: ${res.statusText}`
].join("\n"));
const content = await res.blob();
return content;
}
//#endregion
exports.drawMermaid = drawMermaid;
exports.drawMermaidImage = drawMermaidImage;
//# sourceMappingURL=graph_mermaid.cjs.map