diff --git a/dynamo_viewExtension/Sample View Extension/extra/DynamoAssistantViewExtension_ViewExtensionDefinition.xml b/dynamo_viewExtension/Sample View Extension/extra/DynamoAssistantViewExtension_ViewExtensionDefinition.xml
new file mode 100644
index 0000000..3ec86e8
--- /dev/null
+++ b/dynamo_viewExtension/Sample View Extension/extra/DynamoAssistantViewExtension_ViewExtensionDefinition.xml
@@ -0,0 +1,4 @@
+
+ ..\bin\DynamoAssistantViewExtension.dll
+ DynamoAssistant.DynamoAssistantViewExtension
+
diff --git a/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension.cs b/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension.cs
new file mode 100644
index 0000000..946f67d
--- /dev/null
+++ b/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension.cs
@@ -0,0 +1,87 @@
+using System.Windows.Controls;
+using Dynamo.ViewModels;
+using Dynamo.Wpf.Extensions;
+
+namespace DynamoAssistant
+{
+ ///
+ /// The View Extension framework for Dynamo allows you to extend
+ /// the Dynamo UI by registering custom MenuItems. A ViewExtension has
+ /// two components, an assembly containing a class that implements
+ /// IViewExtension or extends ViewExtensionBase, and a ViewExtensionDefinition
+ /// XML file used to instruct Dynamo where to find the class containing the
+ /// View Extension implementation. The ViewExtensionDefinition XML file must
+ /// be located in your [dynamo]\viewExtensions folder.
+ ///
+ /// This sample demonstrates a View Extension implementation which inherits from
+ /// ViewExtensionBase. It adds a user interface to Dynamo's extension sidebar
+ /// when its MenuItem is clicked.
+ /// The Window created tracks the number of nodes in the current workspace,
+ /// by handling the workspace's NodeAdded and NodeRemoved events.
+ ///
+ public class DynamoAssistantViewExtension : ViewExtensionBase
+ {
+ private MenuItem assistantMenuItem;
+
+ public override void Dispose()
+ {
+ // Do nothing for now
+ }
+
+ public override void Startup(ViewStartupParams p)
+ {
+ }
+
+ public override void Loaded(ViewLoadedParams p)
+ {
+ // Save a reference to your loaded parameters.
+ // You'll need these later when you want to use
+ // the supplied workspaces
+
+ var viewModel = new DynamoAssistantWindowViewModel(p);
+ var window = new DynamoAssistantWindow
+ {
+ DataContext = viewModel,
+ // Set the data context for the main grid in the window.
+ MainGrid = { DataContext = viewModel },
+
+ // Set the owner of the window to the Dynamo window.
+ Owner = p.DynamoWindow
+ };
+ viewModel.dynamoViewModel = p.DynamoWindow.DataContext as DynamoViewModel;
+ assistantMenuItem = new MenuItem { Header = "Dynamo Gen-AI assistant", IsCheckable = true };
+ assistantMenuItem.Checked += (sender, args) => p.AddToExtensionsSideBar(this, window);
+ assistantMenuItem.Unchecked += (sender, args) => p.CloseExtensioninInSideBar(this);
+ p.AddExtensionMenuItem(assistantMenuItem);
+ }
+
+ public override void Shutdown()
+ {
+ }
+
+ public override void Closed()
+ {
+ if (assistantMenuItem != null)
+ {
+ assistantMenuItem.IsChecked = false;
+ }
+ }
+
+ public override string UniqueId
+ {
+ get
+ {
+ return "DA05ED05-6842-4CF0-9ADC-575888A01FEC";
+ }
+ }
+
+ public override string Name
+ {
+ get
+ {
+ return "Gen-AI assistant";
+ }
+ }
+
+ }
+}
diff --git a/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension.csproj b/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension.csproj
new file mode 100644
index 0000000..5101969
--- /dev/null
+++ b/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension.csproj
@@ -0,0 +1,32 @@
+
+
+
+
+
+ DynamoAssistant
+ DynamoAssistantViewExtension
+
+ net8.0-windows
+ true
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension_ViewExtensionDefinition.xml b/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension_ViewExtensionDefinition.xml
new file mode 100644
index 0000000..3ec86e8
--- /dev/null
+++ b/src/DynamoAssistantViewExtension/DynamoAssistantViewExtension_ViewExtensionDefinition.xml
@@ -0,0 +1,4 @@
+
+ ..\bin\DynamoAssistantViewExtension.dll
+ DynamoAssistant.DynamoAssistantViewExtension
+
diff --git a/src/DynamoAssistantViewExtension/DynamoAssistantWindow.xaml b/src/DynamoAssistantViewExtension/DynamoAssistantWindow.xaml
new file mode 100644
index 0000000..47a54c5
--- /dev/null
+++ b/src/DynamoAssistantViewExtension/DynamoAssistantWindow.xaml
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DynamoAssistantViewExtension/DynamoAssistantWindow.xaml.cs b/src/DynamoAssistantViewExtension/DynamoAssistantWindow.xaml.cs
new file mode 100644
index 0000000..321bb33
--- /dev/null
+++ b/src/DynamoAssistantViewExtension/DynamoAssistantWindow.xaml.cs
@@ -0,0 +1,47 @@
+using System.Windows;
+
+namespace DynamoAssistant
+{
+ ///
+ /// Interaction logic for SampleWindow.xaml
+ ///
+ public partial class DynamoAssistantWindow : Window
+ {
+ internal DynamoAssistantWindowViewModel ViewModel => MainGrid.DataContext as DynamoAssistantWindowViewModel;
+ public DynamoAssistantWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void SendButton_Click(object sender, RoutedEventArgs e)
+ {
+ ViewModel.SendMessage(UserInput.Text);
+ ViewModel.UserInput = string.Empty;
+ }
+
+ private void DescribeGraphButton_Click(object sender, RoutedEventArgs e)
+ {
+ ViewModel.DescribeGraph();
+ }
+
+ private void OptimizeGraphButton_Click(object sender, RoutedEventArgs e)
+ {
+ ViewModel.OptimizeGraph();
+ }
+
+ private void WhatsNewButton_Click(object sender, RoutedEventArgs e)
+ {
+ ViewModel.WhatsNew();
+ }
+
+ private void MakeNoteButton_Click(object sender, RoutedEventArgs e)
+ {
+ ViewModel.MakeNote();
+ }
+
+ private void MakeGroupButton_Click(object sender, RoutedEventArgs e)
+ {
+ ViewModel.MakeGroup();
+ }
+ }
+}
diff --git a/src/DynamoAssistantViewExtension/DynamoAssistantWindowViewModel.cs b/src/DynamoAssistantViewExtension/DynamoAssistantWindowViewModel.cs
new file mode 100644
index 0000000..72eb5dd
--- /dev/null
+++ b/src/DynamoAssistantViewExtension/DynamoAssistantWindowViewModel.cs
@@ -0,0 +1,226 @@
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Windows.Input;
+using Dynamo.Core;
+using Dynamo.Extensions;
+using Dynamo.Models;
+using Dynamo.UI.Commands;
+using Dynamo.ViewModels;
+using OpenAI_API;
+using OpenAI_API.Chat;
+using OpenAI_API.Models;
+
+namespace DynamoAssistant
+{
+ public class DynamoAssistantWindowViewModel : NotificationObject, IDisposable
+ {
+ private string userInput;
+ private readonly ReadyParams readyParams;
+ internal DynamoViewModel dynamoViewModel;
+
+ // Chat GPT related fields
+ private readonly OpenAIAPI chatGPTClient;
+ private readonly Conversation conversation;
+ private static readonly string apikey = "Your API Key";
+
+ // Chat GPT pre instruction fields
+ // A set of instructions to prepare GPT to describe Dynamo graph better
+ private const string DescribePreInstruction = "Given a JSON file representing a Dynamo for Revit project, perform a comprehensive analysis focusing on the graph's node structure. Your tasks include:\r\n\r\nReview Node Connections: Ensure each node is connected correctly according to Dynamo's expected data types and functionalities. Identify any instances where inputs may be receiving incorrect data types or where outputs are not utilized efficiently.\r\n\r\nData Type Validation: For each node input and output, validate that the data types are compatible with their intended functions. Highlight mismatches, such as a string data type connected to a numeric input without appropriate conversion.";
+
+ // A set of instructions to prepare GPT to optimize Dynamo graph better
+ private const string OptimizePreInstruction = "Given a JSON file representing a Dynamo for Revit project, perform a comprehensive analysis focusing on the graph's node structure. Your tasks include:\r\n\r\nIdentify Unnecessary Nodes: Detect nodes that do not contribute to the final output or create redundant processes within the graph. This includes nodes with default values that never change or intermediary nodes that could be bypassed without altering the graph's outcome.\r\n\r\nOptimization Recommendations: Based on your analysis, recommend specific changes to the node structure. This might involve reordering nodes for logical flow, changing node types for efficiency, or altering connections to ensure data type compatibility.\r\n\r\nUpdate JSON Structure: Apply the optimization recommendations to the JSON file. Directly modify the \"Nodes\" and \"Connectors\" sections to reflect the optimized graph layout. Ensure that all other elements of the JSON file, such as \"Uuid\", \"Description\", \"ElementResolver\", and metadata, remain unchanged to preserve the file's integrity and additional context.\r\n\r\nOutput an Optimized JSON: Provide a revised JSON file, focusing exclusively on an updated node structure that reflects your analysis and optimizations. This file should retain all original details except for the modifications to nodes and their connections to address identified issues and enhance efficiency.";
+ ///
+ /// User input to the Gen-AI assistant
+ ///
+ public string UserInput
+ {
+ get { return userInput; }
+ set
+ {
+ if (value != null)
+ {
+ // Set the value of the MessageText property
+ userInput = value;
+ // Raise the PropertyChanged event
+ RaisePropertyChanged(nameof(UserInput));
+ }
+ }
+ }
+
+ ///
+ /// Dynamo Model getter
+ ///
+ internal DynamoModel dynamoModel => dynamoViewModel.Model;
+
+ ///
+ /// Is Gopilot waiting for input, this boolean dominates certain UX aspects
+ ///
+ public bool IsWaitingForInput = true;
+
+ public ObservableCollection Messages { get; set; } = new ObservableCollection();
+
+ public DynamoAssistantWindowViewModel(ReadyParams p)
+ {
+ readyParams = p;
+
+ // Create a ChatGPTClient instance with the API key
+ chatGPTClient = new OpenAIAPI(new APIAuthentication(apikey));
+ // ChatGPT lets you start a new chat.
+ conversation = chatGPTClient.Chat.CreateConversation();
+ conversation.Model = Model.GPT4_Turbo;
+ // Adjust this value for more or less "creativity" in the response
+ conversation.RequestParameters.Temperature = 0.1;
+ // Display a welcome message
+ Messages.Add("Gen-AI assistant:\nWelcome to Dynamo world and ask me anything to get started!\n");
+ }
+
+ internal async void SendMessage(string msg)
+ {
+ if (string.IsNullOrEmpty(msg)) return;
+
+ IsWaitingForInput = false;
+ // Display user message first
+ Messages.Add("You:\n" + msg + "\n");
+ // Send the user's input to the ChatGPT API and receive a response
+ conversation?.AppendUserInput(msg);
+ string response = await conversation.GetResponseFromChatbotAsync();
+ // Display the chatbot's response
+ Messages.Add("Gen-AI assistant:\n" + response + "\n");
+
+ var responseToLower = response.ToLower();
+ if (responseToLower.Contains("python script") || responseToLower.Contains("python node"))
+ {
+ CreatePythonNode(response);
+ }
+ IsWaitingForInput = true;
+ }
+
+ internal async void DescribeGraph()
+ {
+ // Set Dynamo file location
+ string filePath = readyParams.CurrentWorkspaceModel.FileName;
+ if (string.IsNullOrEmpty(filePath))
+ {
+ // Alternatively, export Json from current workspace model to continue
+ Messages.Add("Gen-AI assistant:\nPlease save the workspace first.\n");
+ return;
+ }
+
+ //Read the file
+ string jsonData = File.ReadAllText(filePath);
+
+ var msg = "This is my Dynamo project JSON structure.\n" + jsonData;
+
+ // Send the user's input to the ChatGPT API and receive a response
+ conversation?.AppendUserInput(DescribePreInstruction + msg);
+ string response = await conversation.GetResponseFromChatbotAsync();
+ // Display the chatbot's graph description
+ Messages.Add("Gen-AI assistant:\n" + response + "\n");
+ }
+
+ internal async void OptimizeGraph()
+ {
+ // Set Dynamo file location
+ string filePath = readyParams.CurrentWorkspaceModel.FileName;
+ if (string.IsNullOrEmpty(filePath))
+ {
+ // Alternatively, export Json from current workspace model to continue
+ Messages.Add("Gen-AI assistant:\nPlease save the workspace first.\n");
+ return;
+ }
+
+ //Read the file
+ string jsonData = File.ReadAllText(filePath);
+
+ var msg = "This is my Dynamo project JSON structure." + jsonData;
+
+ // Send the user's input to the ChatGPT API and receive a response
+ conversation?.AppendUserInput(OptimizePreInstruction + msg);
+ string response = await conversation.GetResponseFromChatbotAsync();
+ // This file overwrite the original file, please be careful
+ // File.WriteAllText(filePath, response);
+ // Display the chatbot's response
+ Messages.Add("Gen-AI assistant:\n" + response + "\n");
+ }
+
+ internal async void WhatsNew()
+ {
+ // Send the user's input to the ChatGPT API and receive a response
+ conversation?.AppendUserInput("What's new in Dynamo 3.0?");
+ string response = await conversation.GetResponseFromChatbotAsync();
+ // Display the chatbot's response
+ Messages.Add("Gen-AI assistant:\n" + response + "\n");
+ }
+
+ internal void MakeNote()
+ {
+ // create a Dynamo note example
+ CreateNote((new Guid()).ToString(), "This is a sample Note.", 0, 0, true);
+ }
+
+ internal void MakeGroup()
+ {
+ // create a Dynamo group example
+ dynamoModel.ExecuteCommand(new DynamoModel.CreateAnnotationCommand(new Guid(), "This is a sample Group.", string.Empty, 0, 0, true));
+ }
+
+ ///
+ /// Create a python node in Dynamo, use latest Nuget package for this
+ ///
+ ///
+ internal void CreatePythonNode(string response)
+ {
+ string pythonScript = string.Empty;
+ if (response.Contains("```python"))
+ {
+ pythonScript = response.Split("```python")[1];
+ if (pythonScript.Contains("```"))
+ {
+ pythonScript = pythonScript.Split("```")[0];
+ }
+ }
+ else return;
+
+ var pythonNode = new PythonNodeModels.PythonNode
+ {
+ Script = pythonScript
+ };
+ dynamoModel.ExecuteCommand(new DynamoModel.CreateNodeCommand(pythonNode, 0, 0, true, false));
+ Messages.Add("Gen-AI assistant:\nThe Python node including the code above has been created for you!\n");
+ }
+
+ internal void CreateNote(string nodeId, string noteText, double x, double y, bool defaultPosition)
+ {
+ dynamoModel.ExecuteCommand(new DynamoModel.CreateNoteCommand(nodeId, noteText, x, y, defaultPosition));
+ Messages.Add("Gen-AI assistant:\nYour note has been created!\n");
+ }
+
+ private DelegateCommand enterCommand;
+
+ public ICommand EnterCommand
+ {
+ get
+ {
+ enterCommand ??= new DelegateCommand(Enter);
+
+ return enterCommand;
+ }
+ }
+
+ private void Enter(object commandParameter)
+ {
+ SendMessage(commandParameter as string);
+ // Raise event to update the UI and clear the input box
+ UserInput = string.Empty;
+ }
+
+ ///
+ /// Dispose function
+ ///
+ public void Dispose()
+ {
+ // Do nothing
+ }
+ }
+}
diff --git a/src/DynamoAssistantViewExtension/Properties/AssemblyInfo.cs b/src/DynamoAssistantViewExtension/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a04b049
--- /dev/null
+++ b/src/DynamoAssistantViewExtension/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SampleViewExtension")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SampleViewExtension")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("146ebf48-e7a0-4abe-809d-d7f3059e4ee1")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("2.0.1.0")]
+[assembly: AssemblyFileVersion("2.0.1.0")]
diff --git a/src/DynamoSamples.sln b/src/DynamoSamples.sln
index cdd718b..140c2ac 100644
--- a/src/DynamoSamples.sln
+++ b/src/DynamoSamples.sln
@@ -15,7 +15,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleExtension", "SampleEx
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleLinter", "SampleLinter\SampleLinter.csproj", "{5F559FDB-99B9-4F4A-9A91-C9EC94C771D8}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleZeroTouchUnits", "SampleZeroTouchUnits\SampleZeroTouchUnits.csproj", "{4F9ECB35-D321-482A-8ED4-CC8E9342AACE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleZeroTouchUnits", "SampleZeroTouchUnits\SampleZeroTouchUnits.csproj", "{4F9ECB35-D321-482A-8ED4-CC8E9342AACE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamoAssistantViewExtension", "DynamoAssistantViewExtension\DynamoAssistantViewExtension.csproj", "{2B701ADF-1E6F-4FA0-84BC-6FD86E3A62B6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -51,8 +53,15 @@ Global
{4F9ECB35-D321-482A-8ED4-CC8E9342AACE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F9ECB35-D321-482A-8ED4-CC8E9342AACE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F9ECB35-D321-482A-8ED4-CC8E9342AACE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2B701ADF-1E6F-4FA0-84BC-6FD86E3A62B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2B701ADF-1E6F-4FA0-84BC-6FD86E3A62B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2B701ADF-1E6F-4FA0-84BC-6FD86E3A62B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2B701ADF-1E6F-4FA0-84BC-6FD86E3A62B6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F0D2A6B7-67C9-497C-B186-4E19BDD88E1E}
+ EndGlobalSection
EndGlobal