From 123f3f04b1415a63ec2d104a141cfe9bc220fe02 Mon Sep 17 00:00:00 2001 From: blindmansion Date: Sun, 8 Sep 2024 17:34:19 -0400 Subject: [PATCH] Fix issues with named nodes --- packages/cannoli-core/src/factory.ts | 9 +- .../objects/vertices/nodes/ContentNode.ts | 38 ++---- packages/cannoli-core/src/run.ts | 108 ++++++------------ packages/cannoli-core/src/utility.ts | 25 ++++ packages/cannoli-plugin/src/main.ts | 30 +---- .../cannoli-plugin/src/vault_interface.ts | 4 +- 6 files changed, 80 insertions(+), 134 deletions(-) create mode 100644 packages/cannoli-core/src/utility.ts diff --git a/packages/cannoli-core/src/factory.ts b/packages/cannoli-core/src/factory.ts index f7a01e6..4aab635 100644 --- a/packages/cannoli-core/src/factory.ts +++ b/packages/cannoli-core/src/factory.ts @@ -26,6 +26,7 @@ import { AllVerifiedCannoliCanvasNodeData, VerifiedCannoliCanvasEdgeData, } from "./graph"; +import { parseNamedNode } from "./utility"; export enum IndicatedNodeType { Call = "call", @@ -1369,13 +1370,11 @@ export class CannoliFactory { isFloatingNode(id: string): boolean { const node = this.getNode(id); - // Check if the first line starts with [ and ends with ] - const firstLine = node?.text?.split("\n")[0]; - if (firstLine?.startsWith("[") && firstLine?.endsWith("]")) { - return true; - } else { + if (!node?.text) { return false; } + const { name } = parseNamedNode(node.text); + return name !== null; } createRectangle(x: number, y: number, width: number, height: number) { diff --git a/packages/cannoli-core/src/graph/objects/vertices/nodes/ContentNode.ts b/packages/cannoli-core/src/graph/objects/vertices/nodes/ContentNode.ts index 0bf1be3..e8d77aa 100644 --- a/packages/cannoli-core/src/graph/objects/vertices/nodes/ContentNode.ts +++ b/packages/cannoli-core/src/graph/objects/vertices/nodes/ContentNode.ts @@ -5,6 +5,7 @@ import { ChatResponseEdge } from "../../edges/ChatResponseEdge"; import { LoggingEdge } from "../../edges/LoggingEdge"; import { CannoliGroup } from "../CannoliGroup"; import { CannoliNode, VersionedContent } from "../CannoliNode"; +import { parseNamedNode } from "src/utility"; export class ContentNode extends CannoliNode { reset(): void { @@ -24,44 +25,25 @@ export class ContentNode extends CannoliNode { } getName(content?: string): string | null { - let contentToCheck = content; + const contentToCheck = content || this.text; - if (!contentToCheck) { - contentToCheck = this.text; + if (this.type === ContentNodeType.StandardContent) { + return null; } - const firstLine = contentToCheck.split("\n")[0].trim(); - if ( - firstLine.startsWith("[") && - firstLine.endsWith("]") && - this.type !== ContentNodeType.StandardContent - ) { - try { - // Check if the first line is a valid JSON array - JSON.parse(firstLine); - return null; // If it's a valid JSON array, return null - } catch (e) { - // If it's not a valid JSON array, proceed to extract the name - return firstLine.substring(1, firstLine.length - 1); - } - } - return null; + const { name } = parseNamedNode(contentToCheck); + return name; } // Content is everything after the first line getContentCheckName(content?: string): string { - let contentToCheck = content; - - if (!contentToCheck) { - contentToCheck = this.text; - } + const contentToCheck = content || this.text; + const { name, content: parsedContent } = parseNamedNode(contentToCheck); - const name = this.getName(contentToCheck); if (name !== null) { - const firstLine = contentToCheck.split("\n")[0]; - return contentToCheck.substring(firstLine.length + 1); + return parsedContent; } - return this.text; + return contentToCheck; } editContentCheckName(newContent: string): void { diff --git a/packages/cannoli-core/src/run.ts b/packages/cannoli-core/src/run.ts index 1b9b104..07274cd 100644 --- a/packages/cannoli-core/src/run.ts +++ b/packages/cannoli-core/src/run.ts @@ -17,6 +17,7 @@ import { CannoliObject } from "./graph/CannoliObject"; import { CannoliVertex } from "./graph/objects/CannoliVertex"; import { CannoliGroup } from "./graph/objects/vertices/CannoliGroup"; import { CannoliNode } from "./graph/objects/vertices/CannoliNode"; +import { parseNamedNode } from "./utility"; export interface HttpTemplate { id: string; @@ -237,14 +238,18 @@ export class Run { // Find all nodes of type "input" const argNodes = canvasData.nodes.filter((node) => node.cannoliData.type === "input"); - // For each arg, check if the key matches the first line of the text in the input node - for (const arg of Object.entries(this.args ?? {})) { - const [key, value] = arg; - const matchingArgNodes = argNodes.filter((node) => node.cannoliData.text.split("\n")[0] === `[${key}]`); + // For each arg, check if the key matches the name of the input node + for (const [key, value] of Object.entries(this.args ?? {})) { + const matchingArgNodes = argNodes.filter((node) => { + const { name } = parseNamedNode(node.cannoliData.text); + return name === key; + }); + if (matchingArgNodes.length > 0) { - // If so, set the text of each matching input node after the first line to the value + // If matching nodes are found, update their content matchingArgNodes.forEach((argNode) => { - argNode.cannoliData.text = argNode.cannoliData.text.split("\n")[0] + "\n" + value; + const { name } = parseNamedNode(argNode.cannoliData.text); + argNode.cannoliData.text = `[${name}]\n${value}`; }); } else { throw new Error(`Argument key "${key}" not found in input nodes.`); @@ -306,9 +311,9 @@ export class Run { } for (const node of argNodes) { - // Check if the first line of the text contains only a bracketed name - if (/^\[.*?\]$/.test(node.cannoliData.text.split("\n")[0])) { - argNames.add(node.cannoliData.text.split("\n")[0].replace("[", "").replace("]", "")); + const { name } = parseNamedNode(node.cannoliData.text); + if (name !== null) { + argNames.add(name); } } @@ -325,9 +330,9 @@ export class Run { } for (const node of resultNodes) { - // Check if the first line of the text contains only a bracketed name - if (/^\[.*?\]$/.test(node.cannoliData.text.split("\n")[0])) { - resultNames.add(node.cannoliData.text.split("\n")[0].replace("[", "").replace("]", "")); + const { name } = parseNamedNode(node.cannoliData.text); + if (name !== null) { + resultNames.add(name); } } @@ -345,25 +350,19 @@ export class Run { return { names, includesDynamicReference }; } - // For each action node, check if it's a single line for (const node of actionNodes) { - const trimmedText = node.cannoliData.text.trim(); - - if (trimmedText.split("\n").length === 1) { - names.push(trimmedText); + const { name, content } = parseNamedNode(node.cannoliData.text); - if (trimmedText.includes("{{") && trimmedText.includes("}}")) { - includesDynamicReference = true; - } - continue; + if (name !== null) { + names.push(name); + } else if (content.trim().split("\n").length === 1) { + // If it's a single line without a name, treat the whole content as the name + names.push(content.trim()); } - // If it's not a single line, check if the first line is a bracketed name - const firstLine = trimmedText.split("\n")[0]; - const match = firstLine.match(/^\[(.*?)\]$/); - if (match) { - names.push(match[1]); // Push the content inside the brackets - continue; + // Check for dynamic references + if (node.cannoliData.text.includes("{{") && node.cannoliData.text.includes("}}")) { + includesDynamicReference = true; } } @@ -381,13 +380,13 @@ export class Run { for (const edge of incomingConfigProviderEdges || []) { const sourceNode = this.canvasData?.nodes.find((node) => node.id === edge.fromNode); if (sourceNode && (sourceNode.cannoliData.type === ContentNodeType.StandardContent || sourceNode.cannoliData.type === ContentNodeType.Input)) { - // Check if the source node has a bracketed first line - if (/^\[.*?\]$/.test(sourceNode.cannoliData.text.split("\n")[0])) { - providersReferenced.push(sourceNode.cannoliData.text.split("\n")[0].replace("[", "").replace("]", "")); + const { name, content } = parseNamedNode(sourceNode.cannoliData.text); + + if (name !== null) { + providersReferenced.push(name); includesDynamicReference = true; } else { - providersReferenced.push(sourceNode.cannoliData.text.trim()); - + providersReferenced.push(content.trim()); } } else if (sourceNode) { includesDynamicReference = true; @@ -663,12 +662,15 @@ export class Run { getResults(): { [key: string]: string } { const variableNodes = Object.values(this.graph).filter((object) => object.type === "output" && object.kind === "node"); const results: { [key: string]: string } = {}; + for (const node of variableNodes) { - // The key should be the first line without the square brackets - const firstLine = node.text.split("\n")[0]; - const key = firstLine.substring(1, firstLine.length - 1); - results[key] = node.text.split("\n").slice(1).join("\n"); + const { name, content } = parseNamedNode(node.text); + + if (name !== null) { + results[name] = content.trim(); + } } + return results; } @@ -998,40 +1000,6 @@ export class Run { }; } - createHttpTemplate(inputString: string): HttpTemplate { - // Split the input string by newline to separate the name from the JSON - const lines = inputString.split("\n"); - const nameLine = lines[0]; - let jsonString = lines.slice(1).join("\n"); - - // If the json string is in a text block, remove the leading and trailing quotes, as well as the language identifier - if (jsonString.startsWith("```")) { - jsonString = jsonString.substring(3, jsonString.length - 3); - } - - if (jsonString.startsWith("json")) { - jsonString = jsonString.substring(4, jsonString.length); - } - - // Extract the name from the first line - const name = nameLine.substring(1, nameLine.length - 1).trim(); - - // Parse the JSON string - const json = JSON.parse(jsonString); - - // Construct the httpTemplate object - const httpTemplate: HttpTemplate = { - id: "", // Using an empty string for the ID as specified - name: name, - url: json.url, - method: json.method, - headers: json.headers, - bodyTemplate: JSON.stringify(json.bodyTemplate), - }; - - return httpTemplate; - } - logGraph() { for (const node of Object.values(this.graph)) { console.log(node.logDetails()); diff --git a/packages/cannoli-core/src/utility.ts b/packages/cannoli-core/src/utility.ts new file mode 100644 index 0000000..d43477b --- /dev/null +++ b/packages/cannoli-core/src/utility.ts @@ -0,0 +1,25 @@ +export function parseNamedNode(content: string): { + name: string | null; + content: string; +} { + const lines = content.split('\n'); + const firstLine = lines[0].trim(); + + // Check if the first line is a single-bracketed name + if (firstLine.startsWith('[') && firstLine.endsWith(']') && !firstLine.startsWith('[[')) { + // Try to parse as JSON to ensure it's not a valid JSON array + try { + JSON.parse(firstLine); + // If it's a valid JSON array, don't treat it as a name + return { name: null, content }; + } catch { + // Not a valid JSON array, so it's a name + const name = firstLine.slice(1, -1); + const remainingContent = lines.slice(1).join('\n'); + return { name, content: remainingContent }; + } + } + + // If not a single-bracketed name, return null for name and the entire content + return { name: null, content }; +} \ No newline at end of file diff --git a/packages/cannoli-plugin/src/main.ts b/packages/cannoli-plugin/src/main.ts index 78fec4a..d9bcb6d 100644 --- a/packages/cannoli-plugin/src/main.ts +++ b/packages/cannoli-plugin/src/main.ts @@ -572,35 +572,6 @@ export default class Cannoli extends Plugin { } }; - getArgsFromCanvas = (canvas: string) => { - // Parse into JSON - const json = JSON.parse(canvas); - - // Find all nodes with no attatched edges - // Build array of all node IDs referenced in the edges by "toNode" or "fromNode" - const edges = json.edges; - const nodeIds = edges.map((edge: { toNode: string, fromNode: string }) => { - return [edge.toNode, edge.fromNode]; - }).flat(); - - // Look for nodes that are not in the nodeIds array - const nodes = json.nodes; - const noEdgeNodes = nodes.filter((node: { id: string }) => { - return !nodeIds.includes(node.id); - }); - - // Look in the floating nodes for ones whose text has a first line like this "[name]\n", and grab the name - const floatingNodeNames = noEdgeNodes.filter((node: { text?: string }) => { - const firstLine = node.text?.split("\n")?.[0]; - return firstLine?.trim().startsWith("[") && firstLine?.trim().endsWith("]"); - }).map((node: { text: string }) => { - return node.text.trim().slice(1, -1); - }); - - // Return the array - return floatingNodeNames; - } - startActiveCannoliCommand = () => { this.startCannoliCommand(false); }; @@ -1291,6 +1262,7 @@ export default class Cannoli extends Plugin { if (this.encloses(groupRectangle, nodeRectangle)) { nodeIds.push(node.id); } else if (this.overlaps(groupRectangle, nodeRectangle)) { + new Notice(`Invalid layout: Node with id ${node.id} overlaps with the group but is not fully enclosed. Nodes should be fully inside or outside of each group.`); throw new Error( `Invalid layout: Node with id ${node.id} overlaps with the group but is not fully enclosed. Nodes should be fully inside or outside of each group.` ); diff --git a/packages/cannoli-plugin/src/vault_interface.ts b/packages/cannoli-plugin/src/vault_interface.ts index 7c66f4d..e36271a 100644 --- a/packages/cannoli-plugin/src/vault_interface.ts +++ b/packages/cannoli-plugin/src/vault_interface.ts @@ -159,7 +159,7 @@ export class VaultInterface implements FileManager { // If includeLink is true, add the markdown link if (reference.includeLink) { - const link = `[[${file.path}#${reference.subpath}]]`; + const link = `[[${file.path}#${reference.subpath}|${file.basename}]]`; content = link + "\n\n" + content; } @@ -187,7 +187,7 @@ export class VaultInterface implements FileManager { // If includeLink is true, add the markdown link if (reference.includeLink) { - const link = `[[${file.path}]]`; + const link = `[[${file.path}|${file.basename}]]`; content = link + "\n\n" + content; }