├── .assets ├── architecture_stateMachine.png ├── readme_example_1_create_ents.gif ├── readme_example_2_move_to_layer.gif └── readme_example_3_move_to_layer_fix.gif ├── .gitattributes ├── .github └── workflows │ ├── ci-build.yml │ ├── installer-build.yml │ └── release-build-as-zip.yml ├── .gitignore ├── LICENSE ├── README.md ├── Scripture.Installer.Bundle ├── Bundle.wxs └── Scripture.Installer.Bundle.wixproj ├── Scripture.Installer ├── Product.wxs └── Scripture.Installer.wixproj ├── Scripture.sln ├── Scripture ├── Properties │ └── launchSettings.json ├── ScriptExecutor.cs ├── Scripture.csproj ├── ScriptureExtension.cs └── appsettings.json ├── ScriptureCore.Tests ├── RuntimeCompilerTests.cs └── ScriptureCore.Tests.csproj ├── ScriptureCore ├── ConfigurationService.cs ├── ICompiler.cs ├── ILLMServices.cs ├── IScriptExecutor.cs ├── OpenAIService.cs ├── RuntimeCompiler.cs ├── ScriptureCore.csproj ├── ServiceLocator.cs └── ServiceRegistration.cs ├── ScriptureHarnessApp ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── ScriptureHarnessApp.csproj └── appsettings.json └── ScriptureUI ├── Converters └── InverseBooleanConverter.cs ├── ScriptureControl.xaml ├── ScriptureControl.xaml.cs └── ScriptureUI.csproj /.assets/architecture_stateMachine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimitrovakulenko/Scripture/c9592891dd5333cac60fe0f9ed52bec3db3af911/.assets/architecture_stateMachine.png -------------------------------------------------------------------------------- /.assets/readme_example_1_create_ents.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimitrovakulenko/Scripture/c9592891dd5333cac60fe0f9ed52bec3db3af911/.assets/readme_example_1_create_ents.gif -------------------------------------------------------------------------------- /.assets/readme_example_2_move_to_layer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimitrovakulenko/Scripture/c9592891dd5333cac60fe0f9ed52bec3db3af911/.assets/readme_example_2_move_to_layer.gif -------------------------------------------------------------------------------- /.assets/readme_example_3_move_to_layer_fix.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimitrovakulenko/Scripture/c9592891dd5333cac60fe0f9ed52bec3db3af911/.assets/readme_example_3_move_to_layer_fix.gif -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/ci-build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: CI Build 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Setup .NET 21 | uses: actions/setup-dotnet@v4 22 | with: 23 | dotnet-version: 8.0.x 24 | 25 | - name: Restore dependencies 26 | run: dotnet restore 27 | 28 | - name: Build 29 | run: dotnet build --no-restore 30 | 31 | - name: Test 32 | run: dotnet test --no-build --verbosity normal 33 | -------------------------------------------------------------------------------- /.github/workflows/installer-build.yml: -------------------------------------------------------------------------------- 1 | name: Build installers 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write 8 | 9 | jobs: 10 | create-zip: 11 | runs-on: windows-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v4 18 | with: 19 | dotnet-version: 8.0.x 20 | 21 | - name: Setup MSBuild 22 | uses: microsoft/setup-msbuild@v1 23 | 24 | - name: Install WiX Toolset 3.14 25 | run: choco install wixtoolset --version 3.14.0 26 | continue-on-error: true 27 | 28 | - name: Restore dependencies 29 | run: dotnet restore 30 | 31 | - name: Build Release 32 | run: dotnet build --no-restore --configuration Release 33 | 34 | - name: Build MSI Installer 35 | run: msbuild Scripture.Installer/Scripture.Installer.wixproj /p:Configuration=Release 36 | 37 | - name: Build Bundle Installer 38 | run: msbuild Scripture.Installer.Bundle/Scripture.Installer.Bundle.wixproj /p:Configuration=Release 39 | 40 | - name: Set timestamp 41 | id: set_timestamp 42 | run: | 43 | $timestamp = Get-Date -Format "yy-MM-dd-HH-mm" 44 | echo "timestamp=$timestamp" | Out-File -FilePath $env:GITHUB_ENV -Append 45 | shell: pwsh 46 | 47 | - name: Create GitHub Release 48 | id: create_release 49 | uses: actions/create-release@v1 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | tag_name: "v${{ env.timestamp }}" 54 | release_name: "Release ${{ env.timestamp }}" 55 | draft: false 56 | prerelease: false 57 | 58 | - name: Upload MSI Artifact to Release 59 | uses: actions/upload-release-asset@v1 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | with: 63 | upload_url: ${{ steps.create_release.outputs.upload_url }} 64 | asset_path: "Scripture.Installer/bin/x64/Release/ScriptureAutocadPlugin.msi" 65 | asset_name: "ScriptureAutocadPlugin-${{ env.timestamp }}.msi" 66 | label: "MSI Installer" 67 | asset_content_type: "application/octet-stream" 68 | 69 | - name: Upload EXE Artifact to Release 70 | uses: actions/upload-release-asset@v1 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | with: 74 | upload_url: ${{ steps.create_release.outputs.upload_url }} 75 | asset_path: "Scripture.Installer.Bundle/bin/x64/Release/ScriptureInstaller.exe" 76 | asset_name: "ScriptureBundleInstaller-${{ env.timestamp }}.exe" 77 | label: "Bundle Installer" 78 | asset_content_type: "application/octet-stream" 79 | 80 | -------------------------------------------------------------------------------- /.github/workflows/release-build-as-zip.yml: -------------------------------------------------------------------------------- 1 | name: Create Release Zip 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | create-zip: 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Setup .NET 14 | uses: actions/setup-dotnet@v4 15 | with: 16 | dotnet-version: 8.0.x 17 | 18 | - name: Restore dependencies 19 | run: dotnet restore 20 | 21 | - name: Build Release 22 | run: dotnet build --no-restore --configuration Release 23 | 24 | - name: Set timestamp 25 | id: set_timestamp 26 | run: | 27 | $timestamp = Get-Date -Format "yy-MM-dd-HH-mm" 28 | echo "timestamp=$timestamp" | Out-File -FilePath $env:GITHUB_ENV -Append 29 | shell: pwsh 30 | 31 | - name: Upload Zip Archive 32 | uses: actions/upload-artifact@v3 33 | with: 34 | name: ScripturePlugin-${{ env.timestamp }} 35 | path: "./Scripture/bin/x64/Release/net8.0-windows/*" 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | /ScriptureHarnessApp/appsettings.json 365 | /Scripture/appsettings.json 366 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Dmytro Vakulenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCRIPTURE - LLM-Based Automation Plugin for AutoCAD 2 | 3 | SCRIPTURE is a powerful automation tool for AutoCAD that allows users to automate their workflows by simply describing the actions to be automated with text. 4 | 5 | This tool is currently a prototype and is designed for research and experimentation. 6 | 7 | ## Example 8 | 9 | ![Demo of the Plugin: create random entities](.assets/readme_example_1_create_ents.gif) 10 | 11 | ![Demo of the Plugin: move all rects to a new layer](.assets/readme_example_2_move_to_layer.gif) 12 | 13 | ## Prerequisites 14 | 15 | - **AutoCAD** (or similar): Make sure AutoCAD or similar application supporting running .net8 objectARX plugins is installed on your machine 16 | - **Access to an LLM**: Requires an API key and endpoint for either: 17 | - **Microsoft Azure OpenAI API** 18 | - **OpenAI API** (not tested yet) 19 | 20 | ## How to Try It 21 | 22 | ### Install or Compile 23 | 24 | You have two options to get started with SCRIPTURE: 25 | 26 | 1. **Compile Locally** 27 | Clone the repository and build the solution locally using dotnet tool. 28 | 29 | 2. **Install via MSI or Bundle** 30 | 31 | - **MSI Installer**: Use the MSI installer to install the plugin. You need to ensure .NET 8 runtime is already installed. 32 | - **Bundle Installer**: Use the bundle (`bundle.exe`) to install both the .NET 8 runtime (if not installed) and the plugin. 33 | 34 | Please use next link: https://github.com/dimitrovakulenko/Scripture/releases to download an installer. 35 | 36 | ### Configure `appSettings.json` 37 | 38 | You can find 'appSettings.json' file in `C:\Program Files\Scripture Plugin\` (%PROGRAMFILES%) folder in case of using installer and in the bin folder of scripture project in case of local build. 39 | To use SCRIPTURE, you need to configure the `appSettings.json` file. 40 | 41 | - **Azure OpenAI API Configuration**: 42 | 43 | ```json 44 | { 45 | "InitialScriptModel": { 46 | "ApiKey": "YOUR_AZURE_OPENAI_API_KEY", 47 | "Endpoint": "https://YOUR_AZURE_ENDPOINT.openai.azure.com/", 48 | "ModelName": "YOUR_MODEL_DEPLOYMENT_NAME" 49 | } 50 | } 51 | ``` 52 | 53 | - Set ApiKey to your Azure OpenAI API key. 54 | - Set Endpoint to your Azure OpenAI resource endpoint URL. 55 | - Set ModelName to your Azure deployment name. 56 | 57 | - **OpenAI API Configuration**: 58 | 59 | ```json 60 | { 61 | "InitialScriptModel": { 62 | "ApiKey": "YOUR_OPENAI_API_KEY", 63 | "Endpoint": "", 64 | "ModelName": "CHOSEN_MODEL_NAME" 65 | } 66 | } 67 | ``` 68 | 69 | - Set ApiKey to your OpenAI API key. 70 | - Set ModelName to the desired model (e.g., gpt-4o). 71 | - Keep Endpoint empty. 72 | 73 | ### Running the Plugin in AutoCAD 74 | 75 | After installation or compilation, you can run the plugin in AutoCAD: 76 | 77 | - In AutoCAD, use the command `NETLOAD` 78 | - Select `scripture.dll` file from the plugin folder (`C:\Program Files\Scripture Plugin\` in case of full install, binaries folder in case of local build) 79 | 80 | ## Using the Plugin 81 | 82 | Once the plugin is loaded in AutoCAD, you can use it as follows: 83 | 84 | **Step 1: Enter a Prompt and Generate Script** 85 | 86 | Start by entering a prompt that describes the action you want to automate, and click the "Generate Script" button. If the LLM (Language Model) successfully generates the script, you will be automatically navigated to Step 3. 87 | 88 | **Step 3: Execute or Save as Plugin** 89 | 90 | At this stage, you can either execute the script immediately or save it as a separate DLL/plugin for future use. If you choose to save the script, it will be compiled into a custom command dll that can be used in AutoCAD anytime by loading the DLL with the `NETLOAD` command and executing corresponding command. 91 | 92 | **Step 2: Fix Script Errors (Optional)** 93 | 94 | If script generation fails or produces errors, you will be taken to Step 2. 95 | 96 | Here, you can attempt to fix the script automatically by clicking "Try Fix" or manually edit the script using the AvalonEdit control. 97 | 98 | Once you've made changes, click "Recompile Script" to verify if the errors are resolved. 99 | 100 | ## Software Architecture 101 | 102 | ### Software Architecture: Concepts 103 | 104 | - Initial script generation: ask LLM to generate script by description, try compile script, if there are errors ask LLM to fix the errors maximum N times 105 | - Automatic fixing / fine-tuning: provide additional content to LLM when it tries to fix compilation errors, an additional content can be all existing properties and methods of a specific class to avoid hallucinations/unexisting methods creation 106 | 107 | ![Architecture: state machine](.assets/architecture_stateMachine.png) 108 | 109 | ### Software Architecture: Technical 110 | 111 | **The solution** 112 | 113 | Consists of next projects: 114 | - ScriptureCore: contains all core services required by the plugin: script generation service (communication to LLM), script commpilation service (Roslyn API). 115 | - ScriptureCore.Tests: contains tests for the core services 116 | - ScriptureUI: a simple WPF control to host the UI; a proper viewmodel tbd; 117 | - ScriptureHarnessApp: enabled testing fo the UI control without running the complete plugin from Autocad 118 | - Scripture: Autocad .Net plugin, depends on ObjectARX.Net API 119 | 120 | **Installer** 121 | 122 | Installer implemented using Wix 3.1x. 123 | Scripture.Installer project results in plugin msi installer. 124 | Scripture.Bundle.Installer project results in exe file that install plugin and .Net runtime. 125 | 126 | **CI & Release Builds** 127 | 128 | Implemented as github actions 129 | 130 | ## License 131 | 132 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 133 | -------------------------------------------------------------------------------- /Scripture.Installer.Bundle/Bundle.wxs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Scripture.Installer.Bundle/Scripture.Installer.Bundle.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x64 6 | 3.10 7 | 0b0c1701-e398-4762-96e4-70286ec7df6f 8 | 2.0 9 | ScriptureInstaller 10 | Bundle 11 | 12 | 13 | bin\$(Configuration)\ 14 | obj\$(Configuration)\ 15 | Debug 16 | 17 | 18 | bin\$(Configuration)\ 19 | obj\$(Configuration)\ 20 | 21 | 22 | Debug 23 | bin\$(Platform)\$(Configuration)\ 24 | obj\$(Platform)\$(Configuration)\ 25 | 26 | 27 | bin\$(Platform)\$(Configuration)\ 28 | obj\$(Platform)\$(Configuration)\ 29 | 30 | 31 | 32 | 33 | 34 | 35 | $(WixExtDir)\WixBalExtension.dll 36 | WixBalExtension 37 | 38 | 39 | 40 | 41 | WixUtilExtension 42 | WixUtilExtension 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | powershell -Command "If (-Not (Test-Path %27dotnet-runtime-8.0.8-win-x64.exe%27)) { Invoke-WebRequest -Uri %27https://download.visualstudio.microsoft.com/download/pr/cc913baa-9bce-482e-bdfc-56c4b6fafd10/e3f24f2ab2fc02b395c1b67f5193b8d1/dotnet-runtime-8.0.8-win-x64.exe%27 -OutFile %27dotnet-runtime-8.0.8-win-x64.exe%27 }" 52 | 53 | 61 | -------------------------------------------------------------------------------- /Scripture.Installer/Product.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Scripture.Installer/Scripture.Installer.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x64 6 | 3.10 7 | fd30421a-aaea-4457-99c2-ca8766cffe7f 8 | 2.0 9 | ScriptureAutocadPlugin 10 | Package 11 | 12 | 13 | bin\$(Configuration)\ 14 | obj\$(Configuration)\ 15 | Debug 16 | 17 | 18 | bin\$(Configuration)\ 19 | obj\$(Configuration)\ 20 | 21 | 22 | Debug 23 | bin\$(Platform)\$(Configuration)\ 24 | obj\$(Platform)\$(Configuration)\ 25 | 26 | 27 | bin\$(Platform)\$(Configuration)\ 28 | obj\$(Platform)\$(Configuration)\ 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | -------------------------------------------------------------------------------- /Scripture.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.11.35312.102 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scripture", "Scripture\Scripture.csproj", "{5B476D04-2EF5-4495-B38A-80E6103DA77F}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptureUI", "ScriptureUI\ScriptureUI.csproj", "{7DABDA32-5907-4828-BFF2-070811407807}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptureHarnessApp", "ScriptureHarnessApp\ScriptureHarnessApp.csproj", "{A448526D-7CB7-4B2A-8232-EBE6DC1397B2}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptureCore", "ScriptureCore\ScriptureCore.csproj", "{889C6936-11C4-41E3-A8B0-7E15C932F34C}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptureCore.Tests", "ScriptureCore.Tests\ScriptureCore.Tests.csproj", "{B4FF6167-BD4C-47BC-BF99-848C19172467}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Release|Any CPU = Release|Any CPU 21 | Release|x64 = Release|x64 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {5B476D04-2EF5-4495-B38A-80E6103DA77F}.Debug|Any CPU.ActiveCfg = Debug|x64 25 | {5B476D04-2EF5-4495-B38A-80E6103DA77F}.Debug|Any CPU.Build.0 = Debug|x64 26 | {5B476D04-2EF5-4495-B38A-80E6103DA77F}.Debug|x64.ActiveCfg = Debug|x64 27 | {5B476D04-2EF5-4495-B38A-80E6103DA77F}.Debug|x64.Build.0 = Debug|x64 28 | {5B476D04-2EF5-4495-B38A-80E6103DA77F}.Release|Any CPU.ActiveCfg = Release|x64 29 | {5B476D04-2EF5-4495-B38A-80E6103DA77F}.Release|Any CPU.Build.0 = Release|x64 30 | {5B476D04-2EF5-4495-B38A-80E6103DA77F}.Release|x64.ActiveCfg = Release|x64 31 | {5B476D04-2EF5-4495-B38A-80E6103DA77F}.Release|x64.Build.0 = Release|x64 32 | {7DABDA32-5907-4828-BFF2-070811407807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {7DABDA32-5907-4828-BFF2-070811407807}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {7DABDA32-5907-4828-BFF2-070811407807}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {7DABDA32-5907-4828-BFF2-070811407807}.Debug|x64.Build.0 = Debug|Any CPU 36 | {7DABDA32-5907-4828-BFF2-070811407807}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {7DABDA32-5907-4828-BFF2-070811407807}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {7DABDA32-5907-4828-BFF2-070811407807}.Release|x64.ActiveCfg = Release|Any CPU 39 | {7DABDA32-5907-4828-BFF2-070811407807}.Release|x64.Build.0 = Release|Any CPU 40 | {A448526D-7CB7-4B2A-8232-EBE6DC1397B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {A448526D-7CB7-4B2A-8232-EBE6DC1397B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {A448526D-7CB7-4B2A-8232-EBE6DC1397B2}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {A448526D-7CB7-4B2A-8232-EBE6DC1397B2}.Debug|x64.Build.0 = Debug|Any CPU 44 | {A448526D-7CB7-4B2A-8232-EBE6DC1397B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {A448526D-7CB7-4B2A-8232-EBE6DC1397B2}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {A448526D-7CB7-4B2A-8232-EBE6DC1397B2}.Release|x64.ActiveCfg = Release|Any CPU 47 | {A448526D-7CB7-4B2A-8232-EBE6DC1397B2}.Release|x64.Build.0 = Release|Any CPU 48 | {889C6936-11C4-41E3-A8B0-7E15C932F34C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {889C6936-11C4-41E3-A8B0-7E15C932F34C}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {889C6936-11C4-41E3-A8B0-7E15C932F34C}.Debug|x64.ActiveCfg = Debug|Any CPU 51 | {889C6936-11C4-41E3-A8B0-7E15C932F34C}.Debug|x64.Build.0 = Debug|Any CPU 52 | {889C6936-11C4-41E3-A8B0-7E15C932F34C}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {889C6936-11C4-41E3-A8B0-7E15C932F34C}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {889C6936-11C4-41E3-A8B0-7E15C932F34C}.Release|x64.ActiveCfg = Release|Any CPU 55 | {889C6936-11C4-41E3-A8B0-7E15C932F34C}.Release|x64.Build.0 = Release|Any CPU 56 | {B4FF6167-BD4C-47BC-BF99-848C19172467}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {B4FF6167-BD4C-47BC-BF99-848C19172467}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {B4FF6167-BD4C-47BC-BF99-848C19172467}.Debug|x64.ActiveCfg = Debug|Any CPU 59 | {B4FF6167-BD4C-47BC-BF99-848C19172467}.Debug|x64.Build.0 = Debug|Any CPU 60 | {B4FF6167-BD4C-47BC-BF99-848C19172467}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {B4FF6167-BD4C-47BC-BF99-848C19172467}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {B4FF6167-BD4C-47BC-BF99-848C19172467}.Release|x64.ActiveCfg = Release|Any CPU 63 | {B4FF6167-BD4C-47BC-BF99-848C19172467}.Release|x64.Build.0 = Release|Any CPU 64 | EndGlobalSection 65 | GlobalSection(SolutionProperties) = preSolution 66 | HideSolutionNode = FALSE 67 | EndGlobalSection 68 | GlobalSection(ExtensibilityGlobals) = postSolution 69 | SolutionGuid = {75014393-555F-429E-B9FC-3EA1206A2DFE} 70 | EndGlobalSection 71 | EndGlobal 72 | -------------------------------------------------------------------------------- /Scripture/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Scripture": { 4 | "commandName": "Project" 5 | }, 6 | "Acad": { 7 | "commandName": "Executable", 8 | "executablePath": "D:\\ACAD\\venn\\AutoCAD 2025\\acad.exe" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Scripture/ScriptExecutor.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.AutoCAD.ApplicationServices; 2 | using ScriptureCore; 3 | 4 | namespace Scripture 5 | { 6 | internal class ScriptExecutor : IScriptExecutor 7 | { 8 | public void Execute(string dllPath, string commandName) 9 | { 10 | var activeDocument = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument; 11 | 12 | // Set FILEDIA to 0 to suppress file dialog 13 | activeDocument.SendStringToExecute("FILEDIA 0\n", false, false, true); 14 | 15 | // Load the DLL using NETLOAD 16 | activeDocument.SendStringToExecute($"NETLOAD \"{dllPath}\"\n", false, false, true); 17 | 18 | // Restore FILEDIA to 1 19 | activeDocument.SendStringToExecute("FILEDIA 1\n", false, false, true); 20 | 21 | // Execute the command 22 | activeDocument.SendStringToExecute($"{commandName}\n", false, false, true); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Scripture/Scripture.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows 4 | Scripture 5 | enable 6 | enable 7 | x64 8 | false 9 | true 10 | true 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | 30 | $(SolutionDir)ScriptureUI\bin\$(Configuration)\net8.0-windows\ 31 | $(UserProfile)\.nuget\packages\ 32 | $(TargetDir) 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Scripture/ScriptureExtension.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.AutoCAD.Runtime; 2 | using Autodesk.AutoCAD.Windows; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using ScriptureCore; 5 | using ScriptureUI; 6 | using System.Runtime.Versioning; 7 | using System.Windows.Forms.Integration; 8 | 9 | namespace Scripture 10 | { 11 | [SupportedOSPlatform("windows")] 12 | public class ScriptureExtension : IExtensionApplication 13 | { 14 | private PaletteSet? _paletteSet; 15 | 16 | public void Initialize() 17 | { 18 | var serviceCollection = new ServiceCollection(); 19 | ServiceRegistration.RegisterServices(serviceCollection, new ScriptExecutor()); 20 | 21 | // Build the service provider and set it in ServiceLocator 22 | var serviceProvider = serviceCollection.BuildServiceProvider(); 23 | ServiceLocator.SetServiceProvider(serviceProvider); 24 | 25 | // build the palette 26 | _paletteSet = new PaletteSet("Scripture Panel"); 27 | _paletteSet.Visible = true; 28 | _paletteSet.Size = new System.Drawing.Size(600, 600); 29 | _paletteSet.DockEnabled = DockSides.Left; 30 | _paletteSet.Dock = DockSides.Left; 31 | 32 | // Create an instance of the WPF UserControl 33 | var wpfControl = new ScriptureControl(); 34 | 35 | // Create an ElementHost to host the WPF control 36 | ElementHost elementHost = new ElementHost 37 | { 38 | Child = wpfControl, // Assign the WPF UserControl 39 | Dock = DockStyle.Fill // Make the WPF control fill the parent panel 40 | }; 41 | 42 | // Add the ElementHost to a WinForms Panel 43 | Panel panel = new Panel 44 | { 45 | Dock = DockStyle.Fill 46 | }; 47 | panel.Controls.Add(elementHost); 48 | 49 | // Add the panel to the PaletteSet 50 | _paletteSet.Add("Scripture UI", panel); 51 | } 52 | 53 | public void Terminate() 54 | { 55 | 56 | } 57 | 58 | [CommandMethod("ShowScripturePanel")] 59 | public void ShowScripturePanel() 60 | { 61 | if (_paletteSet != null) 62 | { 63 | _paletteSet.Visible = true; // Make the PaletteSet visible 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Scripture/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "InitialScriptModel": { 3 | "ApiKey": "YOURAPIKEY", 4 | "Endpoint": "https://scripture.openai.azure.com", 5 | "DeploymentName": "gpt-4o" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ScriptureCore.Tests/RuntimeCompilerTests.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.AutoCAD.ApplicationServices; 2 | using Autodesk.AutoCAD.DatabaseServices; 3 | using Autodesk.AutoCAD.EditorInput; 4 | using Autodesk.AutoCAD.Geometry; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace ScriptureCore.Tests 8 | { 9 | public class RuntimeCompilerTests 10 | { 11 | public RuntimeCompilerTests() 12 | { 13 | // hacky load of autocad dlls 14 | try 15 | { 16 | Document doc = Application.DocumentManager.MdiActiveDocument; 17 | Editor ed = doc.Editor; 18 | ObjectId objectId = new ObjectId(); 19 | Point3d point3D = new Point3d(); 20 | if (objectId.IsErased) point3D = point3D * 2; 21 | } 22 | catch { } 23 | 24 | // register services 25 | var serviceCollection = new ServiceCollection(); 26 | ServiceRegistration.RegisterServices(serviceCollection, null); 27 | 28 | var serviceProvider = serviceCollection.BuildServiceProvider(); 29 | ServiceLocator.SetServiceProvider(serviceProvider); 30 | } 31 | 32 | [Fact] 33 | public void TestAutocadDllsFound() 34 | { 35 | var code = @" 36 | using Autodesk.AutoCAD.ApplicationServices; 37 | using Autodesk.AutoCAD.DatabaseServices; 38 | using Autodesk.AutoCAD.EditorInput; 39 | using Autodesk.AutoCAD.Geometry; 40 | 41 | namespace AutoCADPlugin 42 | { 43 | public class PolygonSelector 44 | { 45 | public void SelectSmallPolygons() 46 | { 47 | } 48 | } 49 | }"; 50 | 51 | var compiler = ServiceLocator.GetService(); 52 | 53 | var (success, errors) = compiler.TestCompile(code); 54 | 55 | Assert.True(success, $"Compilation failed with errors: {string.Join(Environment.NewLine, errors)}"); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /ScriptureCore.Tests/ScriptureCore.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ScriptureCore/ConfigurationService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System.Reflection; 3 | 4 | namespace ScriptureCore 5 | { 6 | public class ConfigurationService 7 | { 8 | private readonly IConfiguration _configuration; 9 | 10 | public ConfigurationService() 11 | { 12 | var pluginLocation = Path.GetDirectoryName( 13 | Assembly.GetExecutingAssembly().Location); 14 | if (string.IsNullOrEmpty(pluginLocation)) 15 | throw new Exception("Executing dll doesn't exist?"); 16 | 17 | _configuration = new ConfigurationBuilder() 18 | .SetBasePath(pluginLocation) 19 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 20 | .Build(); 21 | } 22 | 23 | public IConfiguration GetConfiguration() => _configuration; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ScriptureCore/ICompiler.cs: -------------------------------------------------------------------------------- 1 | namespace ScriptureCore 2 | { 3 | public interface ICompiler 4 | { 5 | (bool Success, List Errors) TestCompile(string code); 6 | 7 | (bool Success, List Errors) CompileTo(string code, string filepath); 8 | 9 | string GetFullyQualifiedTypeName(string typeName, string code); 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ScriptureCore/ILLMServices.cs: -------------------------------------------------------------------------------- 1 | namespace ScriptureCore 2 | { 3 | public interface ILLMServices 4 | { 5 | Task GenerateInitialScriptAsync(string prompt); 6 | 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// additional data about available properties/methods is provided in some cases 13 | /// 14 | Task TryFixScriptAsync(string script, List errorMessages, bool provideAdditionalMetadata); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ScriptureCore/IScriptExecutor.cs: -------------------------------------------------------------------------------- 1 | namespace ScriptureCore 2 | { 3 | public interface IScriptExecutor 4 | { 5 | public void Execute(string dllPath, string commandName); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ScriptureCore/OpenAIService.cs: -------------------------------------------------------------------------------- 1 | using Azure; 2 | using Azure.AI.OpenAI; 3 | using Microsoft.Extensions.Configuration; 4 | using OpenAI; 5 | using OpenAI.Chat; 6 | using System.ClientModel; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace ScriptureCore 11 | { 12 | internal class OpenAIService : ILLMServices 13 | { 14 | private readonly ChatClient _initialScriptClient; 15 | 16 | public OpenAIService(IConfiguration configuration) 17 | { 18 | string? apiKey = configuration["InitialScriptModel:ApiKey"]; 19 | string? endpoint = configuration["InitialScriptModel:Endpoint"]; 20 | string? modelName = configuration["InitialScriptModel:ModelName"]; 21 | 22 | if (string.IsNullOrEmpty(apiKey)) 23 | { 24 | throw new ArgumentNullException(nameof(apiKey), "API key must not be null or empty."); 25 | } 26 | 27 | // Check if the endpoint is specified to determine if it's Azure or OpenAI 28 | if (string.IsNullOrEmpty(endpoint)) 29 | { 30 | // OpenAI directly 31 | var openAiClient = new OpenAIClient(apiKey); 32 | _initialScriptClient = openAiClient.GetChatClient(modelName); 33 | } 34 | else 35 | { 36 | // Azure OpenAI 37 | if (string.IsNullOrEmpty(modelName)) 38 | { 39 | throw new ArgumentNullException(nameof(modelName), "Deployment name must not be null or empty for Azure."); 40 | } 41 | 42 | var azureClient = new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey)); 43 | _initialScriptClient = azureClient.GetChatClient(modelName); 44 | } 45 | } 46 | 47 | public async Task GenerateInitialScriptAsync(string prompt) 48 | { 49 | ChatCompletionOptions options = new() 50 | { 51 | MaxOutputTokenCount = 1024, 52 | Temperature = 0.3f, 53 | }; 54 | 55 | var messages = new List() 56 | { 57 | new SystemChatMessage(GenerateInitialScriptSystemMessage()), 58 | new UserChatMessage(prompt), 59 | }; 60 | 61 | var completeAnswer = new StringBuilder(); 62 | ClientResult completionsResponse; 63 | 64 | do 65 | { 66 | completionsResponse = await _initialScriptClient.CompleteChatAsync(messages, options); 67 | 68 | // Add the generated response to the complete answer 69 | completeAnswer.Append( 70 | RemoveCodeFence( 71 | string.Join( 72 | "", 73 | completionsResponse.Value.Content.Select(c => c.Text)))); 74 | 75 | // Add the new response to the message list to continue the conversation contextually 76 | messages.Add(new AssistantChatMessage(string.Join("", completionsResponse.Value.Content.Select(c => c.Text)))); 77 | } 78 | while (completionsResponse.Value.FinishReason == ChatFinishReason.Length); 79 | 80 | return completeAnswer.ToString(); 81 | } 82 | 83 | public async Task TryFixScriptAsync(string script, List errorMessages, bool provideAdditionalMetadata) 84 | { 85 | var messages = new List 86 | { 87 | new SystemChatMessage(@"You are an expert C# developer specializing in AutoCAD ObjectARX.NET API. 88 | Your task is to fix the provided script based on the error messages below. 89 | Please ensure: 90 | 1. The corrected version is complete, with all necessary 'using' statements, namespaces, and class definitions. 91 | 2. Return only the corrected C# code, without any extra explanations or comments."), 92 | 93 | new UserChatMessage($"Here is the script that needs fixing:\n```csharp\n{script}\n```"), 94 | new UserChatMessage($"Here are the error messages:\n{string.Join("\n", errorMessages)}") 95 | }; 96 | 97 | if (provideAdditionalMetadata) 98 | { 99 | messages.AddRange(CollectExtraMessagesForErrors(script, errorMessages)); 100 | } 101 | 102 | ChatCompletionOptions options = new() 103 | { 104 | MaxOutputTokenCount = 1024, 105 | Temperature = 0.3f, 106 | }; 107 | 108 | var completeAnswer = new StringBuilder(); 109 | ClientResult completionsResponse; 110 | 111 | do 112 | { 113 | completionsResponse = await _initialScriptClient.CompleteChatAsync(messages, options); 114 | 115 | // Add the generated response to the complete answer 116 | completeAnswer.Append( 117 | RemoveCodeFence( 118 | string.Join( 119 | "", 120 | completionsResponse.Value.Content.Select(c => c.Text)))); 121 | 122 | // Add the new response to the message list to continue the conversation contextually 123 | messages.Add(new AssistantChatMessage(string.Join("", completionsResponse.Value.Content.Select(c => c.Text)))); 124 | } 125 | while (completionsResponse.Value.FinishReason == ChatFinishReason.Length); 126 | 127 | return completeAnswer.ToString(); 128 | } 129 | 130 | private List CollectExtraMessagesForErrors(string script, List errorMessages) 131 | { 132 | var result = new List(); 133 | 134 | var typeNames = new List(); 135 | foreach (var errorMessage in errorMessages) 136 | { 137 | if (Regex.IsMatch(errorMessage, @"'([^']+)' does not contain a definition for '([^']+)'")) 138 | { 139 | var typeName = Regex.Match(errorMessage, @"'([^']+)' does not contain a definition for").Groups[1].Value; 140 | typeNames.Add(typeName); 141 | } 142 | else if (Regex.IsMatch(errorMessage, @"Non-invocable member '([^']+)' cannot be used like a method")) 143 | { 144 | var typeName = Regex.Match(errorMessage, @"Non-invocable member '([^\.]+)\.").Groups[1].Value; 145 | typeNames.Add(typeName); 146 | } 147 | } 148 | 149 | if (typeNames.Any()) 150 | { 151 | var compiler = ServiceLocator.GetService(); 152 | foreach (var typeName in typeNames) 153 | { 154 | var reflectionInfo = GetReflectionInfo( 155 | compiler.GetFullyQualifiedTypeName( 156 | typeName, script)); 157 | if (!string.IsNullOrEmpty(reflectionInfo)) 158 | { 159 | result.Add(new UserChatMessage($"Here is the information about the type '{typeName}':\n{reflectionInfo}")); 160 | } 161 | } 162 | } 163 | 164 | return result; 165 | } 166 | 167 | private string GetReflectionInfo(string typeName) 168 | { 169 | try 170 | { 171 | var type = Type.GetType(typeName); 172 | if (type == null) 173 | { 174 | return string.Empty; 175 | } 176 | 177 | var properties = type.GetProperties(); 178 | var methods = type.GetMethods(); 179 | 180 | var propertyInfo = string.Join(", ", properties.Select(p => $"{p.PropertyType.Name} {p.Name}")); 181 | var methodInfo = string.Join(", ", methods.Select(m => $"{m.ReturnType.Name} {m.Name}()")); 182 | 183 | return $"Properties: {propertyInfo}\nMethods: {methodInfo}"; 184 | } 185 | catch 186 | { 187 | return string.Empty; 188 | } 189 | } 190 | 191 | 192 | private static string RemoveCodeFence(string code) 193 | { 194 | if (code.StartsWith("```csharp", StringComparison.OrdinalIgnoreCase)) 195 | { 196 | code = code.Substring(9).TrimStart(); // Remove ```csharp and any extra whitespace/new lines 197 | } 198 | 199 | if (code.EndsWith("```", StringComparison.OrdinalIgnoreCase)) 200 | { 201 | code = code.Substring(0, code.Length - 3).TrimEnd(); // Remove ``` and any extra whitespace/new lines 202 | } 203 | 204 | return code; 205 | } 206 | 207 | private string GenerateInitialScriptSystemMessage() 208 | { 209 | return @"You are an expert C# developer specializing in AutoCAD ObjectARX.NET API. 210 | Your task is to generate a C# function to solve the user's problem based on the given description. 211 | The function must use ObjectARX.NET classes and methods to accomplish the user's requirements. 212 | Please ensure: 213 | 1. The function is complete, with all necessary 'using' statements, namespaces, and class definitions. 214 | 2. Decompose the problem into multiple sub-functions if needed. 215 | 3. Use only .NET classes related to ObjectARX. 216 | 4. Return only the C# code, without any extra explanations or comments. 217 | 218 | The user's request is: "; 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /ScriptureCore/RuntimeCompiler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | 5 | namespace ScriptureCore 6 | { 7 | internal class RuntimeCompiler: ICompiler 8 | { 9 | public (bool Success, List Errors) TestCompile(string code) 10 | { 11 | var syntaxTree = CSharpSyntaxTree.ParseText(code); 12 | 13 | var references = GetReferences(); 14 | 15 | var compilation = CSharpCompilation.Create("TestCompilation") 16 | .AddReferences(references) 17 | .AddSyntaxTrees(syntaxTree) 18 | .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); 19 | 20 | var result = compilation.Emit(Stream.Null); 21 | 22 | var errors = result.Diagnostics 23 | .Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) 24 | .Select(diagnostic => diagnostic.GetMessage()) 25 | .ToList(); 26 | 27 | return (result.Success, errors); 28 | } 29 | 30 | private List GetReferences() 31 | { 32 | var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); 33 | return loadedAssemblies 34 | .Where(assembly => !assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location)) 35 | .Select(assembly => MetadataReference.CreateFromFile(assembly.Location)) 36 | .ToList(); 37 | } 38 | 39 | public (bool Success, List Errors) CompileTo(string code, string filepath) 40 | { 41 | var syntaxTree = CSharpSyntaxTree.ParseText(code); 42 | 43 | var references = GetReferences(); 44 | 45 | var compilation = CSharpCompilation.Create(Path.GetFileNameWithoutExtension(filepath)) 46 | .AddReferences(references) 47 | .AddSyntaxTrees(syntaxTree) 48 | .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); 49 | 50 | using (var fs = new FileStream(filepath, FileMode.Create)) 51 | { 52 | var result = compilation.Emit(fs); 53 | 54 | var errors = result.Diagnostics 55 | .Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) 56 | .Select(diagnostic => diagnostic.GetMessage()) 57 | .ToList(); 58 | 59 | return (result.Success, errors); 60 | } 61 | } 62 | 63 | public string GetFullyQualifiedTypeName(string typeName, string code) 64 | { 65 | var syntaxTree = CSharpSyntaxTree.ParseText(code); 66 | var root = syntaxTree.GetRoot(); 67 | 68 | var compilation = CSharpCompilation.Create("TempCompilation") 69 | .AddSyntaxTrees(syntaxTree) 70 | .AddReferences( 71 | GetReferences()); 72 | var semanticModel = compilation.GetSemanticModel(syntaxTree); 73 | 74 | var typeNodes = syntaxTree.GetRoot().DescendantNodes().OfType(); 75 | foreach (var typeNode in typeNodes) 76 | { 77 | var symbolInfo = semanticModel.GetSymbolInfo(typeNode); 78 | if (symbolInfo.Symbol != null && symbolInfo.Symbol.Name == typeName) 79 | { 80 | var typeSymbol = symbolInfo.Symbol as ITypeSymbol; 81 | if (typeSymbol != null) 82 | { 83 | return typeSymbol.ToDisplayString(); 84 | } 85 | } 86 | } 87 | return typeName; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /ScriptureCore/ScriptureCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ScriptureCore/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace ScriptureCore 5 | { 6 | public static class ServiceLocator 7 | { 8 | public static IServiceProvider? ServiceProvider { get; private set; } 9 | 10 | public static void SetServiceProvider(IServiceProvider serviceProvider) 11 | { 12 | ServiceProvider = serviceProvider; 13 | } 14 | 15 | public static T GetService() where T : class 16 | { 17 | if (ServiceProvider == null) 18 | { 19 | throw new InvalidOperationException("ServiceProvider is not set. Make sure to call SetServiceProvider during application initialization."); 20 | } 21 | 22 | return ServiceProvider.GetRequiredService(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ScriptureCore/ServiceRegistration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System.Reflection; 3 | 4 | namespace ScriptureCore 5 | { 6 | public static class ServiceRegistration 7 | { 8 | public static void RegisterServices(IServiceCollection services, IScriptExecutor? scriptExecutor) 9 | { 10 | // Register ConfigurationService as a singleton 11 | services.AddSingleton(); 12 | 13 | // Register OpenAIService as a singleton 14 | services.AddSingleton(provider => 15 | { 16 | var configService = provider.GetRequiredService(); 17 | return new OpenAIService(configService.GetConfiguration()); 18 | }); 19 | 20 | services.AddSingleton(); 21 | 22 | if(scriptExecutor != null) 23 | services.AddSingleton(provide => 24 | { 25 | return scriptExecutor; 26 | }); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ScriptureHarnessApp/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ScriptureHarnessApp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.AutoCAD.ApplicationServices; 2 | using Autodesk.AutoCAD.DatabaseServices; 3 | using Autodesk.AutoCAD.EditorInput; 4 | using Autodesk.AutoCAD.Geometry; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using ScriptureCore; 7 | 8 | namespace ScriptureHarnessApp 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : System.Windows.Application 14 | { 15 | public App() 16 | { 17 | string[] args = Environment.GetCommandLineArgs(); 18 | bool loadAutocadDlls = !args.Contains("--noAutoCADDlls"); // Default to true unless explicitly disabled 19 | 20 | // Load AutoCAD DLLs if required 21 | if (loadAutocadDlls) 22 | { 23 | LoadAutoCADDlls(); 24 | } 25 | 26 | var serviceCollection = new ServiceCollection(); 27 | ServiceRegistration.RegisterServices(serviceCollection, null); 28 | 29 | // Build the service provider and set it in ServiceLocator 30 | var serviceProvider = serviceCollection.BuildServiceProvider(); 31 | ServiceLocator.SetServiceProvider(serviceProvider); 32 | } 33 | 34 | private void LoadAutoCADDlls() 35 | { 36 | try 37 | { 38 | Document doc = Application.DocumentManager.MdiActiveDocument; 39 | Editor ed = doc.Editor; 40 | ObjectId objectId = new ObjectId(); 41 | Point3d point3D = new Point3d(); 42 | if (objectId.IsErased) point3D = point3D * 2; 43 | } 44 | catch { } 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /ScriptureHarnessApp/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /ScriptureHarnessApp/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | -------------------------------------------------------------------------------- /ScriptureHarnessApp/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Data; 5 | using System.Windows.Documents; 6 | using System.Windows.Input; 7 | using System.Windows.Media; 8 | using System.Windows.Media.Imaging; 9 | using System.Windows.Navigation; 10 | using System.Windows.Shapes; 11 | 12 | namespace ScriptureHarnessApp 13 | { 14 | /// 15 | /// Interaction logic for MainWindow.xaml 16 | /// 17 | public partial class MainWindow : Window 18 | { 19 | public MainWindow() 20 | { 21 | InitializeComponent(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /ScriptureHarnessApp/ScriptureHarnessApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ScriptureHarnessApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "InitialScriptModel": { 3 | "ApiKey": "YOURAPIKEY", 4 | "Endpoint": "https://scripture.openai.azure.com", 5 | "DeploymentName": "gpt-4o" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ScriptureUI/Converters/InverseBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace ScriptureUI.Converters 5 | { 6 | public class InverseBooleanConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | if (value is bool booleanValue) 11 | { 12 | return !booleanValue; 13 | } 14 | return false; 15 | } 16 | 17 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ScriptureUI/ScriptureControl.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /ScriptureUI/ScriptureControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using ScriptureCore; 2 | using System.ComponentModel; 3 | using System.Configuration; 4 | using System.IO; 5 | using System.Runtime.CompilerServices; 6 | using System.Text.RegularExpressions; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | 10 | namespace ScriptureUI 11 | { 12 | public partial class ScriptureControl : UserControl, INotifyPropertyChanged 13 | { 14 | public ScriptureControl() 15 | { 16 | InitializeComponent(); 17 | 18 | ScriptEditor.Options.IndentationSize = 4; 19 | ScriptEditor.Options.ConvertTabsToSpaces = false; 20 | 21 | _dllPath = ConfigurationManager.AppSettings["DllPath"]; 22 | _dllPath = !string.IsNullOrWhiteSpace(_dllPath) 23 | ? _dllPath 24 | : System.IO.Path.Combine( 25 | Environment.GetFolderPath( 26 | Environment.SpecialFolder.MyDocuments), "AutoCADPlugins", "scripturePlugin.dll"); 27 | 28 | 29 | DataContext = this; 30 | } 31 | 32 | public string? _dllPath; 33 | public string? DllPath 34 | { 35 | get => _dllPath; 36 | set 37 | { 38 | if (_dllPath != value) 39 | { 40 | _dllPath = value; 41 | OnPropertyChanged(); 42 | } 43 | } 44 | } 45 | 46 | private bool _generatingScript = false; 47 | public bool GeneratingScript 48 | { 49 | get => _generatingScript; 50 | set 51 | { 52 | if (_generatingScript != value) 53 | { 54 | _generatingScript = value; 55 | OnPropertyChanged(); 56 | 57 | if (!_generatingScript) 58 | { 59 | ProgressStatusText = ""; 60 | _scriptCompiledWithoutErrors = false; 61 | } 62 | } 63 | } 64 | } 65 | 66 | public bool _scriptCompiledWithoutErrors = false; 67 | public bool ScriptCompiledWithoutErrors 68 | { 69 | get => _scriptCompiledWithoutErrors; 70 | set 71 | { 72 | if (_scriptCompiledWithoutErrors != value) 73 | { 74 | _scriptCompiledWithoutErrors = value; 75 | OnPropertyChanged(); 76 | } 77 | } 78 | } 79 | 80 | public bool _executingScript = false; 81 | public bool ExecutingScript 82 | { 83 | get => _executingScript; 84 | set 85 | { 86 | if (_executingScript != value) 87 | { 88 | _executingScript = value; 89 | OnPropertyChanged(); 90 | } 91 | } 92 | } 93 | 94 | private void SaveDllPathToConfig(string dllPath) 95 | { 96 | var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); 97 | config.AppSettings.Settings.Remove("DllPath"); 98 | config.AppSettings.Settings.Add("DllPath", dllPath); 99 | config.Save(ConfigurationSaveMode.Modified); 100 | 101 | ConfigurationManager.RefreshSection("appSettings"); 102 | } 103 | 104 | private static int _fixErrorAttemptsNumber = 5; 105 | private async void OnGenerateScriptClick(object sender, RoutedEventArgs e) 106 | { 107 | string userPrompt = ScriptDescriptionTextBox.Text; 108 | 109 | if (string.IsNullOrWhiteSpace(userPrompt)) 110 | { 111 | ScriptDescriptionTextBox.Text = "Please enter script descripion here"; 112 | return; 113 | } 114 | 115 | try 116 | { 117 | GeneratingScript = true; 118 | 119 | var llmServices = ServiceLocator.GetService(); 120 | 121 | ProgressStatusText = "Generating initial script"; 122 | var generatedScript = await llmServices.GenerateInitialScriptAsync(userPrompt); 123 | 124 | int attemptIndex = 0; 125 | while (true) 126 | { 127 | ScriptEditor.Text = generatedScript; 128 | 129 | Recompile(); 130 | 131 | if (attemptIndex == _fixErrorAttemptsNumber 132 | || LastCompilationStatus.Success) 133 | { 134 | if (!LastCompilationStatus.Success) 135 | MainTabControl.SelectedIndex = 1; 136 | 137 | break; 138 | 139 | } 140 | 141 | attemptIndex++; 142 | ProgressStatusText = $"Fixing generated script (attempt {attemptIndex} out of {_fixErrorAttemptsNumber})"; 143 | 144 | generatedScript = await llmServices.TryFixScriptAsync(generatedScript, LastCompilationStatus.Errors, false); 145 | } 146 | } 147 | catch (Exception ex) 148 | { 149 | MessageBox.Show(ex.ToString(), "Error"); 150 | } 151 | finally 152 | { 153 | GeneratingScript = false; 154 | } 155 | } 156 | 157 | private (bool Success, List Errors) LastCompilationStatus { get; set; } 158 | 159 | private void Recompile() 160 | { 161 | var compiler = ServiceLocator.GetService(); 162 | 163 | LastCompilationStatus = compiler.TestCompile(ScriptEditor.Text); 164 | ScriptStatusTextBox.Text = LastCompilationStatus.Success 165 | ? "Successfully compiled" 166 | : $"Compilation failed:{Environment.NewLine} " + 167 | $"{string.Join(Environment.NewLine, LastCompilationStatus.Errors)}"; 168 | 169 | if (LastCompilationStatus.Success) 170 | { 171 | MainTabControl.SelectedIndex = 2; 172 | 173 | var commandName = ExtractCommandName(ScriptEditor.Text); 174 | if (commandName != null) 175 | { 176 | CommandNameTextBox.Text = commandName; 177 | } 178 | } 179 | 180 | ScriptCompiledWithoutErrors = LastCompilationStatus.Success; 181 | } 182 | 183 | private void OnRecompileScriptClick(object sender, RoutedEventArgs e) 184 | { 185 | Recompile(); 186 | } 187 | 188 | private async void OnTryFixClick(object sender, RoutedEventArgs e) 189 | { 190 | try 191 | { 192 | GeneratingScript = true; 193 | 194 | var llmServices = ServiceLocator.GetService(); 195 | 196 | string script = ScriptEditor.Text; 197 | 198 | if (LastCompilationStatus.Errors.Count == 0) 199 | return; 200 | 201 | ProgressStatusText = "Fixing generated script"; 202 | var fixedScript = await llmServices.TryFixScriptAsync(script, LastCompilationStatus.Errors, true); 203 | 204 | if (!string.IsNullOrEmpty(fixedScript)) 205 | { 206 | ScriptEditor.Text = fixedScript; 207 | 208 | Recompile(); 209 | } 210 | else 211 | throw new Exception("No reply"); 212 | } 213 | catch (Exception ex) 214 | { 215 | MessageBox.Show(ex.ToString(), "Error"); 216 | } 217 | finally 218 | { 219 | GeneratingScript = false; 220 | } 221 | } 222 | 223 | private void OnExecutionModeChanged(object sender, RoutedEventArgs e) 224 | { 225 | if (PluginOptionsPanel is null) 226 | return; 227 | 228 | if (((RadioButton)sender).IsChecked ?? false) 229 | { 230 | var radioButton = sender as RadioButton; 231 | 232 | if (radioButton?.Content.ToString() == "Create Plugin") 233 | { 234 | PluginOptionsPanel.Visibility = Visibility.Visible; 235 | ExecuteScriptButton.Content = "Create Plugin"; 236 | } 237 | else 238 | { 239 | PluginOptionsPanel.Visibility = Visibility.Collapsed; 240 | ExecuteScriptButton.Content = "Execute Script"; 241 | } 242 | } 243 | } 244 | 245 | private void OnExecuteScriptClick(object sender, RoutedEventArgs e) 246 | { 247 | try 248 | { 249 | string script = ScriptEditor.Text; 250 | var commandName = ExtractCommandName(script); 251 | if (commandName is null) 252 | throw new Exception("Command name not found"); 253 | 254 | var compiler = ServiceLocator.GetService(); 255 | 256 | if (CreatePluginRadioButton.IsChecked ?? false) 257 | { 258 | if (string.IsNullOrWhiteSpace(DllPath)) 259 | { 260 | var res = MessageBox.Show("Enter dll name please", "Error"); 261 | return; 262 | } 263 | 264 | if (File.Exists(DllPath)) 265 | { 266 | var res = MessageBox.Show("The dll already exists, overwrite?", "Warning", MessageBoxButton.YesNo); 267 | if (res == MessageBoxResult.No) 268 | return; 269 | } 270 | 271 | SaveDllPathToConfig(DllPath); 272 | 273 | var customCommandName = CommandNameTextBox.Text; 274 | if (!string.IsNullOrWhiteSpace(customCommandName)) 275 | { 276 | script = ReplaceCommandName(script, commandName, customCommandName); 277 | commandName = customCommandName; 278 | } 279 | 280 | var (success, errors) = compiler.CompileTo(script, DllPath); 281 | 282 | MessageBox.Show(success ? "Succeeded" : $"Failed:\n {string.Join(',', errors)}", "Result"); 283 | } 284 | else 285 | { 286 | var tempFileName = Path.Combine(Path.GetTempPath(), $"GeneratedScript_{Guid.NewGuid()}.dll"); 287 | 288 | var (success, errors) = compiler.CompileTo(script, tempFileName); 289 | 290 | if (!success) 291 | { 292 | MessageBox.Show($"Failed to compile:#\n{string.Join(',', errors)}", "Result"); 293 | return; 294 | } 295 | 296 | var scriptExecutor = ServiceLocator.GetService(); 297 | scriptExecutor.Execute(tempFileName, commandName); 298 | } 299 | } 300 | catch (Exception ex) 301 | { 302 | MessageBox.Show(ex.ToString(), "Error"); 303 | } 304 | } 305 | 306 | private string _progressStatusText = ""; 307 | public string ProgressStatusText 308 | { 309 | get => _progressStatusText; 310 | set 311 | { 312 | if (_progressStatusText != value) 313 | { 314 | _progressStatusText = value; 315 | OnPropertyChanged(); 316 | } 317 | } 318 | } 319 | 320 | private string? ExtractCommandName(string script) 321 | { 322 | var match = Regex.Match(script, @"\[CommandMethod\(""([^""]+)""\)\]"); 323 | return match.Success ? match.Groups[1].Value : null; 324 | } 325 | 326 | private string ReplaceCommandName(string script, string oldCommandName, string newCommandName) 327 | { 328 | return script.Replace($"[CommandMethod(\"{oldCommandName}\")]", $"[CommandMethod(\"{newCommandName}\")]"); 329 | } 330 | 331 | public event PropertyChangedEventHandler? PropertyChanged; 332 | 333 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) 334 | { 335 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /ScriptureUI/ScriptureUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0-windows 5 | enable 6 | true 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------