Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/DynamoCoreWpf/DynamoCoreWpf.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UILib>true</UILib>
</PropertyGroup>
Expand Down Expand Up @@ -1308,6 +1308,7 @@
<Resource Include="UI\Images\closetab_hover.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="UI\Images\cluster_32px.png" />
<Resource Include="UI\Images\gripper-default.png" />
<Resource Include="UI\Images\gripper-hover.png" />
<Resource Include="UI\Images\caret-left-disabled.png" />
Expand Down
Binary file added src/DynamoCoreWpf/UI/Images/cluster_32px.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
using Dynamo.Core;
using Dynamo.Graph.Workspaces;
using Dynamo.Graph;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Dynamo.NodeAutoComplete.ViewModels
{
Expand Down Expand Up @@ -346,6 +348,7 @@ public bool DisplayLowConfidence
internal event Action<NodeModel> ParentNodeRemoved;

internal MLNodeClusterAutoCompletionResponse FullResults { private set; get; }
internal List<SingleAutocompleteResult> FullSingleResults { set; get; }

/// <summary>
/// Constructor
Expand Down Expand Up @@ -472,14 +475,14 @@ internal MLNodeAutoCompletionRequest GenerateRequestForMLAutocomplete()
return request;
}

internal IEnumerable<NodeSearchElementViewModel> GetNodeAutocompleMLResults()
private IEnumerable<SingleAutocompleteResult> GetNodeAutocompleMLResults()
{
MLNodeAutoCompletionResponse MLresults = null;

// Get results from the ML API.
try
{
MLresults = GetMLNodeAutocompleteResults();
MLresults = GetGenericAutocompleteResult<MLNodeAutoCompletionResponse>(nodeAutocompleteMLEndpoint);
}
catch (Exception ex)
{
Expand All @@ -488,7 +491,7 @@ internal IEnumerable<NodeSearchElementViewModel> GetNodeAutocompleMLResults()
AutocompleteMLTitle = Resources.LoginNeededTitle;
AutocompleteMLMessage = Resources.LoginNeededMessage;
Analytics.TrackEvent(Actions.View, Categories.NodeAutoCompleteOperations, "UnabletoFetch");
return new List<NodeSearchElementViewModel>();
return new List<SingleAutocompleteResult>();
}

// no results
Expand All @@ -498,10 +501,10 @@ internal IEnumerable<NodeSearchElementViewModel> GetNodeAutocompleMLResults()
AutocompleteMLTitle = Resources.AutocompleteNoRecommendationsTitle;
AutocompleteMLMessage = Resources.AutocompleteNoRecommendationsMessage;
Analytics.TrackEvent(Actions.View, Categories.NodeAutoCompleteOperations, "NoRecommendation");
return new List<NodeSearchElementViewModel>();
return new List<SingleAutocompleteResult>();
}
ServiceVersion = MLresults.Version;
var results = new List<NodeSearchElementViewModel>();
var results = new List<SingleAutocompleteResult>();

var zeroTouchSearchElements = Model.Entries.OfType<ZeroTouchSearchElement>().Where(x => x.IsVisibleInSearch);
var nodeModelSearchElements = Model.Entries.OfType<NodeModelSearchElement>().Where(x => x.IsVisibleInSearch);
Expand Down Expand Up @@ -541,13 +544,9 @@ internal IEnumerable<NodeSearchElementViewModel> GetNodeAutocompleMLResults()
}
}

var viewModelElement = GetViewModelForNodeSearchElement(nodeSearchElement);
var viewModelElement = new SingleAutocompleteResult(nodeSearchElement, result.Score);

if (viewModelElement != null)
{
viewModelElement.AutoCompletionNodeMachineLearningInfo = new AutoCompletionNodeMachineLearningInfo(true, true, Math.Round(result.Score * 100));
results.Add(viewModelElement);
}
results.Add(viewModelElement);
}
// Matching known node types of node-model nodes.
else if (Enum.IsDefined(typeof(NodeModelNodeTypes), result.Node.Type.NodeType))
Expand All @@ -574,19 +573,16 @@ internal IEnumerable<NodeSearchElementViewModel> GetNodeAutocompleMLResults()
};
}

var viewModelElement = GetViewModelForNodeSearchElement(nodeSearchElement);
var viewModelElement = new SingleAutocompleteResult(nodeSearchElement, result.Score);

if (viewModelElement != null)
{
viewModelElement.AutoCompletionNodeMachineLearningInfo = new AutoCompletionNodeMachineLearningInfo(true, true, Math.Round(result.Score * 100));
results.Add(viewModelElement);
}

results.Add(viewModelElement);
}
}

return results;
}
internal T GetGenericAutocompleteResult<T>(string endpoint)
private T GetGenericAutocompleteResult<T>(string endpoint)
{
var requestDTO = GenerateRequestForMLAutocomplete();
var jsonRequest = JsonConvert.SerializeObject(requestDTO);
Expand Down Expand Up @@ -642,16 +638,6 @@ internal T GetGenericAutocompleteResult<T>(string endpoint)
return results;
}

private MLNodeAutoCompletionResponse GetMLNodeAutocompleteResults()
{
return GetGenericAutocompleteResult<MLNodeAutoCompletionResponse>(nodeAutocompleteMLEndpoint);
}

private MLNodeClusterAutoCompletionResponse GetMLNodeClusterAutocompleteResults()
{
return GetGenericAutocompleteResult<MLNodeClusterAutoCompletionResponse>(nodeClusterAutocompleteMLEndpoint);
}

/// <summary>
/// Show the low confidence ML results.
/// </summary>
Expand Down Expand Up @@ -708,7 +694,7 @@ internal HostNames GetHostNameEnum(string HostName)
/// <summary>
/// Key function to populate node autocomplete results to display
/// </summary>
internal IEnumerable<NodeSearchElementViewModel> GetSingleAutocompleteResults()
internal IEnumerable<SingleAutocompleteResult> GetSingleAutocompleteResults()
{
if (PortViewModel == null) return null;

Expand All @@ -734,11 +720,11 @@ internal IEnumerable<NodeSearchElementViewModel> GetSingleAutocompleteResults()
// These default suggestions will be populated based on the port type.
if (!objectTypeMatchingElements.Any())
{
return DefaultAutoCompleteCandidates();
return DefaultAutoCompleteCandidates().Select(x => new SingleAutocompleteResult(x));
}
else
{
return GetViewModelForNodeSearchElements(objectTypeMatchingElements);
return objectTypeMatchingElements.Select(x => new SingleAutocompleteResult(x));
}
}
}
Expand Down Expand Up @@ -894,38 +880,37 @@ internal void PopulateAutoComplete()
{
DropdownResults = null;
}

Task.Run(() =>
{
IEnumerable<NodeAutoCompleteClusterResult> comboboxResults;
if (IsSingleAutocomplete)
{
var fullSingleResults = GetSingleAutocompleteResults().ToList();
FullSingleResults = GetSingleAutocompleteResults().ToList();
FullResults = new MLNodeClusterAutoCompletionResponse
{
Version = "0.0",
NumberOfResults = fullSingleResults.Count,
Results = fullSingleResults.Select(x => new ClusterResultItem
NumberOfResults = FullSingleResults.Count,
Results = FullSingleResults.Select(x => new ClusterResultItem
{
Description = x.Description,
Title = x.Description,
Probability = (x.AutoCompletionNodeMachineLearningInfo.ConfidenceScore / 100).ToString(),
Title = x.Description,
Probability = x.Score.ToString(),
EntryNodeIndex = 0,
EntryNodeInPort = x.Model.AutoCompletionNodeElementInfo.PortToConnect,
EntryNodeInPort = x.PortToConnect,
Topology = new TopologyItem
{
Nodes = new List<NodeItem> { new NodeItem {
Id = new Guid().ToString(),
Type = new NodeType { Id = x.Model.CreationName } } },
Type = new NodeType { Id = x.CreationName } } },
Connections = new List<ConnectionItem>()
}
})
};
}
else
{
FullResults = GetMLNodeClusterAutocompleteResults();
FullResults = GetGenericAutocompleteResult<MLNodeClusterAutoCompletionResponse>(nodeClusterAutocompleteMLEndpoint);
}
comboboxResults = QualifiedResults.Select(x => new NodeAutoCompleteClusterResult { Description = x.Description });

dynamoViewModel.UIDispatcher.BeginInvoke(() =>
{
Expand All @@ -936,6 +921,35 @@ internal void PopulateAutoComplete()
return;
}

IEnumerable<NodeAutoCompleteClusterResult> comboboxResults;
if (IsSingleAutocomplete)
{
//getting bitmaps from resources necessarily has to be done in the UI thread
Dictionary<string, ImageSource> dict = [];
foreach (var singleResult in FullSingleResults)
{
if (dict.ContainsKey(singleResult.CreationName))
{
continue;
}
var iconRequest = new IconRequestEventArgs(singleResult.Assembly, singleResult.IconName + Configurations.SmallIconPostfix);
SearchViewModelRequestBitmapSource(iconRequest);
dict[singleResult.CreationName] = iconRequest.Icon;
}
comboboxResults = QualifiedResults.Select(x => new NodeAutoCompleteClusterResult
{
Description = x.Description,
SmallIcon = dict[x.Topology.Nodes.First().Type.Id],
});
}
else
{
comboboxResults = QualifiedResults.Select(x => new NodeAutoCompleteClusterResult
{
Description = x.Description
//default icon (cluster) is set in the xaml view
});
}
// this runs synchronously on the UI thread, so the UI can't disappear during execution
DropdownResults = comboboxResults;
SelectedIndex = 0;
Expand Down Expand Up @@ -992,84 +1006,6 @@ internal void NodeViewModel_Removed(NodeModel node)
ParentNodeRemoved?.Invoke(node);
}

/// <summary>
/// Returns a IEnumberable of NodeSearchElementViewModel for respective NodeSearchElements.
/// </summary>
private IEnumerable<NodeSearchElementViewModel> GetViewModelForNodeSearchElements(List<NodeSearchElement> searchElementsCache)
{
return searchElementsCache.Select(e =>
{
var vm = new NodeSearchElementViewModel(e, this);
vm.RequestBitmapSource += SearchViewModelRequestBitmapSource;
return vm;
});
}

/// <summary>
/// Returns a NodeSearchElementViewModel for a NodeSearchElement
/// </summary>
private NodeSearchElementViewModel GetViewModelForNodeSearchElement(NodeSearchElement nodeSearchElement)
{
if (nodeSearchElement != null)
{
var vm = new NodeSearchElementViewModel(nodeSearchElement, this);
vm.RequestBitmapSource += SearchViewModelRequestBitmapSource;
return vm;
}
return null;
}


/// <summary>
/// Performs a search using the given string as query and subset, if provided.
/// </summary>
/// <returns> Returns a list with a maximum MaxNumSearchResults elements.</returns>
/// <param name="search"> The search query </param>
internal IEnumerable<NodeSearchElementViewModel> SearchNodeAutocomplete(string search)
{
if (LuceneUtility != null)
{
//The DirectoryReader and IndexSearcher have to be assigned after commiting indexing changes and before executing the Searcher.Search() method, otherwise new indexed info won't be reflected
LuceneUtility.dirReader = LuceneUtility.writer?.GetReader(applyAllDeletes: true);
if (LuceneUtility.dirReader == null) return null;

LuceneUtility.Searcher = new IndexSearcher(LuceneUtility.dirReader);

string searchTerm = search.Trim();
var candidates = new List<NodeSearchElementViewModel>();
var parser = new MultiFieldQueryParser(LuceneConfig.LuceneNetVersion, LuceneConfig.NodeIndexFields, LuceneUtility.Analyzer)
{
AllowLeadingWildcard = true,
DefaultOperator = LuceneConfig.DefaultOperator,
FuzzyMinSim = LuceneConfig.MinimumSimilarity
};

Query query = parser.Parse(LuceneUtility.CreateSearchQuery(LuceneConfig.NodeIndexFields, searchTerm));
TopDocs topDocs = LuceneUtility.Searcher.Search(query, n: LuceneConfig.DefaultResultsCount);

for (int i = 0; i < topDocs.ScoreDocs.Length; i++)
{
// read back a Lucene doc from results
Document resultDoc = LuceneUtility.Searcher.Doc(topDocs.ScoreDocs[i].Doc);

string name = resultDoc.Get(nameof(LuceneConfig.NodeFieldsEnum.Name));
string docName = resultDoc.Get(nameof(LuceneConfig.NodeFieldsEnum.DocName));
string cat = resultDoc.Get(nameof(LuceneConfig.NodeFieldsEnum.FullCategoryName));
string parameters = resultDoc.Get(nameof(LuceneConfig.NodeFieldsEnum.Parameters));


var foundNode = FindViewModelForNodeNameAndCategory(name, cat, parameters);
if (foundNode != null)
{
candidates.Add(foundNode);
}
}

return candidates;
}
return null;
}

/// <summary>
/// Returns a collection of node search elements for nodes
/// that output a type compatible with the port type if it's an input port.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.Windows.Media;

namespace Dynamo.NodeAutoComplete.ViewModels
{
public class NodeAutoCompleteClusterResult
{
public string Description { get; set; }
public ImageSource SmallIcon { get; set; }
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this file to match the other one?
NodeAutoCompleteClusterResult.

So this one and the class would be NodeAutoCompleteSingleResult?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we could rename the other one to be simpler too, Eg. ClusterAutoCompleteResult and SingleAutoCompleteResult

Copy link
Contributor Author

@chubakueno chubakueno May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They have slightly different purposes, NodeAutoCompleteClusterResult is a view model for the dropdown (for both single and cluster) and SingleAutocompleteResult extracts the key necessary information from a NodeSearchElementViewModel.

So i'm renaming SingleAutocompleteResult to SingleResultItem (analog to ClusterResultItem from src\DynamoCore\Search\NodeAutocompleteSearch.cs) and NodeAutoCompleteClusterResult to DNADropdownViewModel

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Dynamo.Search.SearchElements;
using Dynamo.Wpf.ViewModels;

namespace Dynamo.NodeAutoComplete.ViewModels
{
internal class SingleAutocompleteResult
{

public SingleAutocompleteResult(NodeSearchElement model, double score = 1.0)
{
Assembly = model.Assembly;
IconName = model.IconName;
Description = model.Description;
CreationName = model.CreationName;
PortToConnect = model.AutoCompletionNodeElementInfo.PortToConnect;
Score = score;
}

public SingleAutocompleteResult(NodeSearchElementViewModel x) : this(x.Model)
{
//Convert percent to probability
Score = x.AutoCompletionNodeMachineLearningInfo.ConfidenceScore / 100.0;
}

internal string Assembly { get; set; }

internal string IconName { get; set; }

internal string Description { get; set; }

internal string CreationName { get; set; }

internal int PortToConnect { get; set; }

internal double Score { get; set; }
}
}
Loading
Loading