From 5cf8dd32a27c9b866fa08d62aec3afd4eb1561ac Mon Sep 17 00:00:00 2001 From: Ankitkumar Satapara Date: Fri, 24 Apr 2026 00:44:57 +0530 Subject: [PATCH] Added logs to track the issues, started doing changes based on the school api project --- .../Execution/Handlers/NodeHandlers.cs | 2 +- .../Execution/Resolvers/NodeValueResolvers.cs | 26 +++++++- Examples/Nodify.Calculator/Executor.cs | 64 ++++++++++++++++++- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/Examples/Nodify.Calculator/Execution/Handlers/NodeHandlers.cs b/Examples/Nodify.Calculator/Execution/Handlers/NodeHandlers.cs index 0aae73a..1d29b73 100644 --- a/Examples/Nodify.Calculator/Execution/Handlers/NodeHandlers.cs +++ b/Examples/Nodify.Calculator/Execution/Handlers/NodeHandlers.cs @@ -198,7 +198,7 @@ namespace Nodify.Calculator.Execution.Handlers var bodyNode = loopBodyConnection?.Input?.Operation; if (bodyNode != null) - ctx.Executor.TraverseChainPublic(bodyNode, "end", ctx.Connections, new HashSet(), true); + ctx.Executor.ExecuteLinearChain(bodyNode, ctx.Connections); } ctx.Log($"[FOREACH] Loop completed. {array.Count} iterations executed."); } diff --git a/Examples/Nodify.Calculator/Execution/Resolvers/NodeValueResolvers.cs b/Examples/Nodify.Calculator/Execution/Resolvers/NodeValueResolvers.cs index b757b85..13a8411 100644 --- a/Examples/Nodify.Calculator/Execution/Resolvers/NodeValueResolvers.cs +++ b/Examples/Nodify.Calculator/Execution/Resolvers/NodeValueResolvers.cs @@ -56,13 +56,35 @@ namespace Nodify.Calculator.Execution.Resolvers { // Split's model input is the Square-shaped connector var modelInput = node.Input.FirstOrDefault(i => i.Shape == ConnectorShape.Square); - if (modelInput == null) return null; + if (modelInput == null) + { + ctx.Log("[SPLIT] No Square input connector found.", logType.Warning); + return null; + } var modelJson = ctx.Read(modelInput); + ctx.Log($"[SPLIT] Input JSON: {(modelJson?.Length > 120 ? modelJson.Substring(0, 120) + "..." : modelJson ?? "(null)")}"); if (string.IsNullOrWhiteSpace(modelJson)) return null; var propName = ResolverUtils.NormalizeConnectorName(outputConnector.Title); - return ResolverUtils.ExtractJsonProperty(modelJson, propName); + ctx.Log($"[SPLIT] Looking for property: \"{propName}\""); + + // Try direct property extraction first + var result = ResolverUtils.ExtractJsonProperty(modelJson, propName); + + // If not found at top level, try unwrapping a "data" envelope + if (result == null) + { + var dataInner = ResolverUtils.ExtractJsonProperty(modelJson, "data"); + if (!string.IsNullOrWhiteSpace(dataInner)) + { + ctx.Log("[SPLIT] Property not found at top level, unwrapping \"data\" envelope..."); + result = ResolverUtils.ExtractJsonProperty(dataInner, propName); + } + } + + ctx.Log($"[SPLIT] Resolved \"{propName}\" = {result ?? "(null)"}"); + return result; } } diff --git a/Examples/Nodify.Calculator/Executor.cs b/Examples/Nodify.Calculator/Executor.cs index 1385f24..4184cce 100644 --- a/Examples/Nodify.Calculator/Executor.cs +++ b/Examples/Nodify.Calculator/Executor.cs @@ -208,24 +208,51 @@ namespace Nodify.Calculator var sourceOp = sourceConnector?.Operation; var srcNodeId = sourceOp?.NodeId; + OnLogMe?.Invoke($"[RESOLVE] Reading input \"{input.Title}\" ? source node \"{sourceOp?.Title}\" (connector \"{sourceConnector?.Title}\")"); + // 1. Flow-executed nodes record their result in outputs[nodeId]. if (srcNodeId != null && outputs.TryGetValue(srcNodeId, out var producedVal)) + { + OnLogMe?.Invoke($"[RESOLVE] Found in outputs[{sourceOp?.Title}]: {(producedVal?.Length > 80 ? producedVal.Substring(0, 80) + "..." : producedVal)}"); return producedVal; + } // 2. Generic dispatch for non-flow (sideband) nodes. if (sourceOp != null && sourceConnector != null) { + OnLogMe?.Invoke($"[RESOLVE] No cached output for \"{sourceOp.Title}\", trying value resolvers..."); var resCtx = new ValueResolutionContext(this, connections); foreach (var resolver in _valueResolvers) { if (!resolver.CanResolve(sourceOp, sourceConnector)) continue; + OnLogMe?.Invoke($"[RESOLVE] Matched resolver: {resolver.GetType().Name}"); var resolved = resolver.Resolve(sourceOp, sourceConnector, resCtx); if (!string.IsNullOrEmpty(resolved)) + { + OnLogMe?.Invoke($"[RESOLVE] Resolver returned: {(resolved.Length > 80 ? resolved.Substring(0, 80) + "..." : resolved)}"); return resolved; + } + OnLogMe?.Invoke($"[RESOLVE] Resolver returned null/empty, trying next...", logType.Warning); + } + OnLogMe?.Invoke($"[RESOLVE] No resolver produced a value for \"{sourceOp.Title}\"", logType.Warning); + + // 2b. Fallback: if no resolver handled this non-flow node, try to recursively + // resolve its first data input and pass the value through (generic passthrough). + var fallbackInput = sourceOp.Input.FirstOrDefault(i => i.Shape != ConnectorShape.Triangle); + if (fallbackInput != null) + { + OnLogMe?.Invoke($"[RESOLVE] Attempting generic passthrough for \"{sourceOp.Title}\"..."); + var passthrough = TryReadInputValue(fallbackInput, connections); + if (!string.IsNullOrEmpty(passthrough)) + { + OnLogMe?.Invoke($"[RESOLVE] Passthrough returned: {(passthrough.Length > 80 ? passthrough.Substring(0, 80) + "..." : passthrough)}"); + return passthrough; + } } } // 3/4. Fall back to connector/default values. + OnLogMe?.Invoke($"[RESOLVE] Falling back to connector Value: {sourceConnector?.Value}", logType.Warning); if (sourceConnector?.Value != null) return sourceConnector.Value.ToString(); return input.Value.ToString(); @@ -479,10 +506,43 @@ namespace Nodify.Calculator internal string GetResponsePublic(string url, string type) => GetResponse(url, type); internal bool TraverseChainPublic(OperationViewModel node, string endNodeTitle, - ICollection connections, - HashSet visited, bool isExecute) + ICollection connections, + HashSet visited, bool isExecute) => TraverseChain(node, endNodeTitle, connections, visited, isExecute); + /// + /// Executes a linear chain of nodes without requiring an "End" node. + /// Used for loop bodies where the chain simply terminates at the last node. + /// + internal void ExecuteLinearChain(OperationViewModel startNode, + ICollection connections) + { + var visited = new HashSet(); + var current = startNode; + while (current != null) + { + if (visited.Contains(current.NodeId)) + break; + visited.Add(current.NodeId); + + SetNodeState(current, ExecutionState.Running); + System.Threading.Thread.Sleep(AnimationDelayMs / 2); + StartExecution(current, connections); + SetNodeState(current, ExecutionState.Completed); + + // Find next node via outgoing Triangle connection + var outConn = connections.FirstOrDefault(c => c.Output?.Operation == current && c.Output.Shape == ConnectorShape.Triangle); + if (outConn?.Input?.Operation == null) + break; + + SetConnectionActive(outConn, true); + System.Threading.Thread.Sleep(AnimationDelayMs); + SetConnectionActive(outConn, false); + + current = outConn.Input.Operation; + } + } + internal void AddNewModelPublic(OperationInfoViewModel model) => editorViewModel.Calculator.OperationsMenu.AddNewModel(model);