├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── Directory.Build.props ├── LICENSE.txt ├── OpenAI_NET ├── FodyWeavers.xml ├── OpenAI_NET.csproj ├── Program.cs ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── functions │ ├── Audio.cs │ └── Image.cs ├── ico │ └── voiceattack.ico ├── packages.lock.json └── service │ ├── Logging.cs │ └── Piping.cs ├── OpenAI_VoiceAttack_Plugin.sln ├── OpenAI_VoiceAttack_Plugin ├── GlobalSuppressions.cs ├── OpenAI_VoiceAttack_Plugin.csproj ├── OpenAIplugin.cs ├── Properties │ └── AssemblyInfo.cs ├── appendix │ ├── All-VoiceAttack-Variables.txt │ ├── history │ │ └── chatgpt_response.txt │ ├── images │ │ ├── openai_plugin_dalle_example.png │ │ └── openai_plugin_mask_example.png │ ├── systems │ │ └── SystemPrompt_ChatGPT_TTS.txt │ └── tuning │ │ └── fine-tuning-template.JSON ├── context │ ├── ChatGPT.cs │ ├── Completion.cs │ ├── DallE.cs │ ├── Embedding.cs │ ├── Files.cs │ ├── KeyForm.cs │ ├── Moderation.cs │ └── Whisper.cs ├── packages.config ├── packages.lock.json └── service │ ├── Configuration.cs │ ├── Dictation.cs │ ├── Logging.cs │ ├── ModelsGPT.cs │ ├── OpenAI_Key.cs │ ├── OpenAI_NET.cs │ ├── Piping.cs │ └── Updates.cs └── README.md /.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/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: SemlerPDX 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | -------------------------------------------------------------------------------- /OpenAI_NET/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /OpenAI_NET/OpenAI_NET.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | OpenAI_NET App 9 | Companion App for the OpenAI VoiceAttack Plugin 10 | Copyright © 2023 SemlerPDX 11 | VG Labs 12 | by SemlerPDX 13 | ico\voiceattack.ico 14 | OpenAI VoiceAttack Plugin 15 | 16 | 17 | 18 | embedded 19 | 20 | 21 | 22 | embedded 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | all 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | True 42 | True 43 | Resources.resx 44 | 45 | 46 | 47 | 48 | 49 | ResXFileCodeGenerator 50 | Resources.Designer.cs 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /OpenAI_NET/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenAI_NET.service; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Timers; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Collections.Generic; 8 | using System.Net; 9 | 10 | namespace OpenAI_NET 11 | { 12 | /// 13 | /// OpenAI_NET App for the OpenAI API VoiceAttack Plugin 14 | ///
Copyright (C) 2023 Aaron Semler
15 | ///
github.com/SemlerPDX
16 | ///
veterans-gaming.com/semlerpdx-avcs
17 | /// 18 | /// A background console app listening to OpenAI VoiceAttack Plugin requests for OpenAI API Whisper and Dall-E: 19 | ///
-Can process audio via transcription or translation into (English) text using OpenAI Whisper.
20 | ///
-Can generate or work with images using the OpenAI Dall-E API, returning a list of URL's.
21 | ///
22 | /// 23 | /// This program is free software: you can redistribute it and/or modify
24 | /// it under the terms of the GNU General Public License as published by
25 | /// the Free Software Foundation, either version 3 of the License, or
26 | /// (at your option) any later version.
27 | ///
28 | /// This program is distributed in the hope that it will be useful,
29 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
30 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 | /// GNU General Public License for more details.
32 | ///
33 | /// You should have received a copy of the GNU General Public License
34 | /// along with this program. If not, see gnu.org/licenses. 35 | ///
36 | ///
37 | internal class Program 38 | { 39 | [DllImport("kernel32.dll")] 40 | static extern IntPtr GetConsoleWindow(); 41 | 42 | [DllImport("user32.dll")] 43 | static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 44 | 45 | const int SW_HIDE = 0; 46 | 47 | 48 | private static System.Timers.Timer? aTimer; 49 | 50 | static void Main() 51 | { 52 | var handle = GetConsoleWindow(); 53 | ShowWindow(handle, SW_HIDE); 54 | 55 | AppDomain.CurrentDomain.ProcessExit += new EventHandler(OnProcessExit); 56 | 57 | Logging.SetErrorLogPath(); 58 | 59 | InitializeProcessTimer(); 60 | 61 | while (true) 62 | { 63 | Run(); 64 | } 65 | } 66 | 67 | private static void OnProcessExit(object? sender, EventArgs e) 68 | { 69 | if (aTimer != null) 70 | { 71 | aTimer.Stop(); 72 | aTimer.Dispose(); 73 | } 74 | if (Environment.ExitCode != 0) 75 | Logging.WriteToLogFile($"OpenAI_NET Application exited with code {Environment.ExitCode}"); 76 | } 77 | 78 | /// 79 | /// This method initiates a 2 second timer to monitor if VoiceAttack is running. 80 | /// 81 | private static void InitializeProcessTimer() 82 | { 83 | // Create a timer with a two second interval. 84 | aTimer = new System.Timers.Timer(2000D); 85 | 86 | // Hook up the Elapsed event for the timer. 87 | aTimer.Elapsed += CheckVoiceAttackProcess; 88 | aTimer.AutoReset = true; 89 | aTimer.Enabled = true; 90 | } 91 | 92 | /// 93 | /// This method ensures that the OpenAI_NET application cannot continue running if VoiceAttack is closed.
94 | /// When Plugin Support is disabled in VoiceAttack Options, the OpenAI Plugin can no longer close this app,
95 | /// this will do so the next time VoiceAttack closes. 96 | ///
97 | /// The source of the event. 98 | /// The ElapsedEventArgs object that contains the event data. 99 | private static void CheckVoiceAttackProcess(Object? source, ElapsedEventArgs e) 100 | { 101 | if (!Process.GetProcessesByName("VoiceAttack").Any()) 102 | { 103 | if (aTimer != null && aTimer.Enabled) 104 | { 105 | aTimer.Stop(); 106 | aTimer.Dispose(); 107 | } 108 | Environment.Exit(0); 109 | } 110 | } 111 | 112 | /// 113 | /// This main Run method listens for Whisper and Dall-E commands over the Named Pipe with OpenAI Plugin for VoiceAttack, 114 | /// and then executes those instructions in the or class. 115 | /// 116 | private static async void Run() 117 | { 118 | while (true) 119 | { 120 | try 121 | { 122 | string[] args = Piping.ListenForArgsOnNamedPipe(); 123 | if (args != null && args.Length != 0 && args.Length >= 1) 124 | { 125 | switch (args[0]) 126 | { 127 | case "transcribe": 128 | if (args.Length == 3) { await Audio.Transcribe(args); } 129 | break; 130 | case "translate": 131 | if (args.Length == 3) { await Audio.Translate(args); } 132 | break; 133 | case "image.generate": 134 | if (args.Length >= 3) { await Image.GenerateImage(args); } 135 | break; 136 | case "image.variation": 137 | if (args.Length >= 3) { await Image.VariateImage(args); } 138 | break; 139 | case "image.variation.bytes": 140 | if (args.Length >= 3) { await Image.VariateImage(args, true); } 141 | break; 142 | case "image.edit": 143 | if (args.Length >= 4) { await Image.EditImage(args); } 144 | break; 145 | case "image.edit.bytes": 146 | if (args.Length >= 4) { await Image.EditImage(args, true); } 147 | break; 148 | default: 149 | break; 150 | } 151 | } 152 | } 153 | catch (Exception ex) 154 | { 155 | try 156 | { 157 | try 158 | { 159 | // Log the error to file 160 | Logging.WriteToLogFile(ex.Message); 161 | 162 | // Send the error message back to the OpenAI VoiceAttack Plugin 163 | if (!Piping.SendArgsToNamedPipe(new[] { ex.Message.ToString(), "error" })) 164 | { 165 | Console.WriteLine($"Failed to send previous error message through pipe: {ex.Message}"); 166 | throw new Exception($"Failed to send previous error message through pipe: {ex.Message}"); 167 | } 168 | } 169 | catch (ArgumentException aex) 170 | { 171 | Console.WriteLine($"OpenAI_NET Error: {aex.Message} Previous Error: {ex.Message}"); 172 | Logging.WriteToLogFile($"OpenAI_NET Error: {aex.Message} Previous Error: {ex.Message}"); 173 | } 174 | } 175 | catch 176 | { 177 | Console.WriteLine($"OpenAI_NET Error: Unable to send previous error message through pipe: {ex.Message}"); 178 | Logging.WriteToLogFile($"OpenAI_NET Error: Unable to send previous error message through pipe: {ex.Message}"); 179 | } 180 | } 181 | GC.Collect(); 182 | } 183 | } 184 | 185 | } 186 | } -------------------------------------------------------------------------------- /OpenAI_NET/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace OpenAI_NET.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenAI_NET.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /OpenAI_NET/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | text/microsoft-resx 91 | 92 | 93 | 1.3 94 | 95 | 96 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 97 | 98 | 99 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 100 | 101 | -------------------------------------------------------------------------------- /OpenAI_NET/functions/Audio.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using OpenAI.Net; 4 | using OpenAI_NET.service; 5 | 6 | namespace OpenAI_NET 7 | { 8 | /// 9 | /// A class providing methods to transform audio with OpenAI Audio using its API. 10 | ///
11 | ///
See Also:
12 | ///
13 | ///
14 | /// 15 | ///
OpenAI_NET App for the OpenAI API VoiceAttack Plugin
16 | ///
Copyright (C) 2023 Aaron Semler
17 | ///
github.com/SemlerPDX
18 | ///
veterans-gaming.com/semlerpdx-avcs
19 | ///

20 | /// This program is free software: you can redistribute it and/or modify
21 | /// it under the terms of the GNU General Public License as published by
22 | /// the Free Software Foundation, either version 3 of the License, or
23 | /// (at your option) any later version.
24 | ///
25 | /// This program is distributed in the hope that it will be useful,
26 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
27 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 | /// GNU General Public License for more details.
29 | ///
30 | /// You should have received a copy of the GNU General Public License
31 | /// along with this program. If not, see gnu.org/licenses. 32 | ///
33 | public static class Audio 34 | { 35 | /// 36 | /// Transcribe audio into text using the OpenAI API and return it to the OpenAI VoiceAttack Plugin. 37 | /// 38 | /// An array of strings that contains the function, 39 | /// the OpenAI API Key, and the path to the audio file. 40 | /// Directly returns a string array containing the transcribed text 41 | /// through the Named Pipe to the OpenAI VoiceAttack Plugin. 42 | public static async Task Transcribe(string[] args) 43 | { 44 | try 45 | { 46 | // Set Organization ID if contained in the API key within args 47 | string argsOrg = String.Empty; 48 | if (args.Length > 0) 49 | { 50 | if (args[1].Contains(':')) 51 | { 52 | string[] argsKey = args[1].Split(":"); 53 | args[1] = argsKey[0]; 54 | argsOrg = argsKey[1]; 55 | } 56 | } 57 | 58 | // Set the host with or without organization id 59 | using var host = String.IsNullOrEmpty(argsOrg) 60 | ? Host.CreateDefaultBuilder(args) 61 | .ConfigureServices((builder, services) => 62 | { 63 | services.AddOpenAIServices(options => 64 | { 65 | options.ApiKey = args[1]; 66 | }); 67 | }) 68 | .Build() 69 | : Host.CreateDefaultBuilder(args) 70 | .ConfigureServices((builder, services) => 71 | { 72 | services.AddOpenAIServices(options => 73 | { 74 | options.ApiKey = args[1]; 75 | options.OrganizationId = argsOrg; 76 | }); 77 | }) 78 | .Build(); 79 | 80 | var openAi = host.Services.GetService()!; 81 | 82 | var transcription = await openAi.Audio.GetTranscription(args[2]); 83 | 84 | if (String.IsNullOrEmpty(transcription.Result!.Text)) 85 | throw new Exception($"Transcription Error - results text is null or empty!"); 86 | 87 | // Send the transcribed text back to the OpenAI VoiceAttack Plugin 88 | Piping.SendArgsToNamedPipe(new[] { transcription.Result!.Text }); 89 | } 90 | catch (Exception ex) 91 | { 92 | Console.WriteLine($"OpenAI_NET Transcription Error! {ex.Message}"); 93 | 94 | // Send the error message back to the OpenAI VoiceAttack Plugin 95 | throw new Exception($"Whisper Transcription Error: {ex.Message}"); 96 | } 97 | } 98 | 99 | /// 100 | /// Translates non-English audio into English using the OpenAI API, 101 | /// then return it to the OpenAI VoiceAttack Plugin. 102 | /// 103 | /// An array of strings that contains the function, 104 | /// the OpenAI API Key, and the path to the audio file. 105 | /// Directly returns a string array containing the translated text 106 | /// through the Named Pipe to the OpenAI VoiceAttack Plugin. 107 | public static async Task Translate(string[] args) 108 | { 109 | try 110 | { 111 | using var host = Host.CreateDefaultBuilder(args) 112 | .ConfigureServices((builder, services) => 113 | { 114 | services.AddOpenAIServices(options => 115 | { 116 | options.ApiKey = args[1]; 117 | }); 118 | }) 119 | .Build(); 120 | 121 | var openAi = host.Services.GetService()!; 122 | var translation = await openAi.Audio.GetTranslation(args[2]); 123 | 124 | // Send the translated text back to the OpenAI VoiceAttack Plugin 125 | Piping.SendArgsToNamedPipe(new[] { translation.Result!.Text, "success" }); 126 | } 127 | catch (Exception ex) 128 | { 129 | Console.WriteLine($"OpenAI_NET Translation Error! {ex.Message}"); 130 | 131 | // Send the error message back to the OpenAI VoiceAttack Plugin 132 | throw new Exception($"Whisper Translation Error: {ex.Message}"); 133 | } 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /OpenAI_NET/functions/Image.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using OpenAI.Net; 4 | using OpenAI_NET.service; 5 | 6 | namespace OpenAI_NET 7 | { 8 | /// 9 | /// A class providing methods to work with images using OpenAI Dall-E through its API. 10 | ///
11 | ///
See Also:
12 | ///
13 | ///
14 | /// 15 | ///
OpenAI_NET App for the OpenAI API VoiceAttack Plugin
16 | ///
Copyright (C) 2023 Aaron Semler
17 | ///
github.com/SemlerPDX
18 | ///
veterans-gaming.com/semlerpdx-avcs
19 | ///

20 | /// This program is free software: you can redistribute it and/or modify
21 | /// it under the terms of the GNU General Public License as published by
22 | /// the Free Software Foundation, either version 3 of the License, or
23 | /// (at your option) any later version.
24 | ///
25 | /// This program is distributed in the hope that it will be useful,
26 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
27 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 | /// GNU General Public License for more details.
29 | ///
30 | /// You should have received a copy of the GNU General Public License
31 | /// along with this program. If not, see gnu.org/licenses. 32 | ///
33 | public static class Image 34 | { 35 | /// 36 | /// Generate image(s) based on a supplied prompt, and any additional parameters. 37 | /// 38 | /// An array of strings that contains the function, 39 | /// the OpenAI API Key, and any additional image options. 40 | /// Directly returns a string array containing URL(s) to DALL-E generated 41 | /// image(s) through the Named Pipe to the OpenAI VoiceAttack Plugin. 42 | public static async Task GenerateImage(string[] args) 43 | { 44 | try 45 | { 46 | // Set Organization ID if contained in the API key within args 47 | string argsOrg = String.Empty; 48 | if (args.Length > 0) 49 | { 50 | if (args[1].Contains(':')) 51 | { 52 | string[] argsKey = args[1].Split(":"); 53 | args[1] = argsKey[0]; 54 | argsOrg = argsKey[1]; 55 | } 56 | } 57 | 58 | // Set the host with or without organization id 59 | using var host = String.IsNullOrEmpty(argsOrg) 60 | ? Host.CreateDefaultBuilder(args) 61 | .ConfigureServices((builder, services) => 62 | { 63 | services.AddOpenAIServices(options => 64 | { 65 | options.ApiKey = args[1]; 66 | }); 67 | }) 68 | .Build() 69 | : Host.CreateDefaultBuilder(args) 70 | .ConfigureServices((builder, services) => 71 | { 72 | services.AddOpenAIServices(options => 73 | { 74 | options.ApiKey = args[1]; 75 | options.OrganizationId = argsOrg; 76 | }); 77 | }) 78 | .Build(); 79 | 80 | // Set DALL-E request parameters or use defaults 81 | string imagePrompt = args.Length > 2 && !String.IsNullOrEmpty(args[2]) ? args[2] : String.Empty; 82 | Int32 imageCount = args.Length > 3 && !String.IsNullOrEmpty(args[3]) ? Int32.TryParse(args[3], out int count) ? count : 1 : 1; 83 | string imageSize = args.Length > 4 && !String.IsNullOrEmpty(args[4]) ? args[4] : "1024x1024"; 84 | 85 | if (String.IsNullOrEmpty(imagePrompt)) { throw new Exception("Image Generation Error: Image User Prompt is null or empty!"); } 86 | 87 | if (!(imageSize == "256x256" || imageSize == "512x512" || imageSize == "1024x1024")) 88 | { 89 | throw new Exception("Image Generation Error: Image size invalid!"); 90 | } 91 | 92 | if (imageCount > 10) 93 | imageCount = 10; 94 | 95 | var openAi = host.Services.GetService(); 96 | if (openAi != null) 97 | { 98 | var response = await openAi.Images.Generate(imagePrompt, imageCount, imageSize); 99 | 100 | if (response.IsSuccess) 101 | { 102 | List Results = response.Result!.Data.Select(i => i.Url).ToList(); 103 | 104 | // Send the URL(s) of the image(s) back to the OpenAI VoiceAttack Plugin 105 | Piping.SendArgsToNamedPipe(Results.ToArray()); 106 | } 107 | else 108 | { 109 | Console.WriteLine($"{response.ErrorMessage}"); 110 | 111 | // Send the error message back to the OpenAI VoiceAttack Plugin 112 | throw new Exception($"Image Generation Error: {response.ErrorMessage}"); 113 | } 114 | } 115 | } 116 | catch (Exception ex) 117 | { 118 | Console.WriteLine(ex.ToString()); 119 | 120 | // Send the error message back to the OpenAI VoiceAttack Plugin 121 | throw new Exception($"Dall-E Exception: {ex.Message}"); 122 | } 123 | 124 | } 125 | 126 | /// 127 | /// Upload an image from the supplied file path and create variation(s) 128 | /// based on a supplied prompt, and any additional parameters. 129 | /// 130 | /// An array of strings that contains the function, 131 | /// the OpenAI API Key, and any additional image options. 132 | /// Directly returns a string array containing URL(s) to DALL-E variated 133 | /// image(s) through the Named Pipe to the OpenAI VoiceAttack Plugin. 134 | public static async Task VariateImage(string[] args) { await VariateImage(args, false); } 135 | /// 136 | /// Upload an image from the supplied file path and create variation(s) 137 | /// based on a supplied prompt, and any additional parameters. 138 | /// 139 | /// An array of strings that contains the function, 140 | /// the OpenAI API Key, and any additional image options. 141 | /// A boolean directing the method to upload the image using bytes. 142 | /// Directly returns a string array containing URL(s) to DALL-E variated 143 | /// image(s) through the Named Pipe to the OpenAI VoiceAttack Plugin. 144 | public static async Task VariateImage(string[] args, bool useBytes) 145 | { 146 | try 147 | { 148 | using var host = Host.CreateDefaultBuilder() 149 | .ConfigureServices((builder, services) => 150 | { 151 | services.AddOpenAIServices(options => 152 | { 153 | options.ApiKey = args[1]; 154 | }); 155 | }) 156 | .Build(); 157 | 158 | // Set DALL-E request parameters or use defaults 159 | string imagePath = args.Length > 2 && !String.IsNullOrEmpty(args[2]) ? args[2] : String.Empty; 160 | Int32 imageCount = args.Length > 3 && !String.IsNullOrEmpty(args[3]) ? Int32.TryParse(args[3], out int count) ? count : 1 : 1; 161 | string imageSize = args.Length > 4 && !String.IsNullOrEmpty(args[4]) ? args[4] : "1024x1024"; 162 | 163 | if (String.IsNullOrEmpty(imagePath)) { throw new Exception("Image Variation Error: File Path to Image is null or empty!"); } 164 | if (!(imageSize == "256x256" || imageSize == "512x512" || imageSize == "1024x1024")) 165 | { 166 | throw new Exception("Image Variation Error: Image size invalid!"); 167 | } 168 | 169 | if (imageCount > 10) 170 | imageCount = 10; 171 | 172 | var openAi = host.Services.GetService(); 173 | if (openAi != null) 174 | { 175 | var response = useBytes 176 | ? await openAi.Images.Variation(File.ReadAllBytes(imagePath), o => 177 | { 178 | o.N = imageCount; 179 | o.Size = imageSize; 180 | }) 181 | : await openAi.Images.Variation(imagePath, o => 182 | { 183 | o.N = imageCount; 184 | o.Size = imageSize; 185 | }); 186 | 187 | if (response.IsSuccess) 188 | { 189 | List Results = response.Result!.Data.Select(i => i.Url).ToList(); 190 | 191 | // Send the URL(s) of the image(s) back to the OpenAI VoiceAttack Plugin 192 | Piping.SendArgsToNamedPipe(Results.ToArray()); 193 | } 194 | else 195 | { 196 | Console.WriteLine($"{response.ErrorMessage}"); 197 | 198 | // Send the error message back to the OpenAI VoiceAttack Plugin 199 | throw new Exception($"Image Variation Error: {response.ErrorMessage}"); 200 | } 201 | } 202 | } 203 | catch (Exception ex) 204 | { 205 | Console.WriteLine(ex.ToString()); 206 | 207 | // Send the error message back to the OpenAI VoiceAttack Plugin 208 | throw new Exception($"Dall-E Exception: {ex.Message}"); 209 | } 210 | } 211 | 212 | /// 213 | /// Upload an image from the supplied file path and create edits 214 | /// based on a supplied prompt, an optional mask file path, and any additional parameters. 215 | /// 216 | /// An array of strings that contains the function, 217 | /// the OpenAI API Key, and any additional image options. 218 | /// Directly returns a string array containing URL(s) to DALL-E edited 219 | /// image(s) through the Named Pipe to the OpenAI VoiceAttack Plugin. 220 | public static async Task EditImage(string[] args) { await EditImage(args, false); } 221 | /// 222 | /// Upload an image from the supplied file path and create edits 223 | /// based on a supplied prompt, an optional mask file path, and any additional parameters. 224 | /// 225 | /// An array of strings that contains the function, 226 | /// the OpenAI API Key, and any additional image options. 227 | /// A boolean directing the method to upload the image using bytes. 228 | /// Directly returns a string array containing URL(s) to DALL-E edited 229 | /// image(s) through the Named Pipe to the OpenAI VoiceAttack Plugin. 230 | public static async Task EditImage(string[] args, bool useBytes) 231 | { 232 | try 233 | { 234 | using var host = Host.CreateDefaultBuilder() 235 | .ConfigureServices((builder, services) => 236 | { 237 | services.AddOpenAIServices(options => 238 | { 239 | options.ApiKey = args[1]; 240 | }); 241 | }) 242 | .Build(); 243 | 244 | // Set DALL-E request parameters or use defaults 245 | string imagePrompt = args.Length > 2 && !String.IsNullOrEmpty(args[2]) ? args[2] : String.Empty; 246 | string imagePath = args.Length > 3 && !String.IsNullOrEmpty(args[3]) ? args[3] : String.Empty; 247 | Int32 imageCount = args.Length > 4 && !String.IsNullOrEmpty(args[4]) ? Int32.TryParse(args[4], out int count) ? count : 1 : 1; 248 | string imageSize = args.Length > 5 && !String.IsNullOrEmpty(args[5]) ? args[5] : "1024x1024"; 249 | string maskPath = args.Length > 6 && !String.IsNullOrEmpty(args[6]) ? args[6] : String.Empty; 250 | 251 | if (String.IsNullOrEmpty(imagePrompt)) { throw new Exception("Image Edit Error: Image User Prompt is null or empty!"); } 252 | if (String.IsNullOrEmpty(imagePath)) { throw new Exception("Image Edit Error: File Path to Image is null or empty!"); } 253 | 254 | 255 | if (!(imageSize == "256x256" || imageSize == "512x512" || imageSize == "1024x1024")) 256 | { 257 | throw new Exception("Image Editing Error: Image size invalid!"); 258 | } 259 | 260 | if (imageCount > 10) 261 | imageCount = 10; 262 | 263 | var openAi = host.Services.GetService(); 264 | if (openAi != null) 265 | { 266 | var response = useBytes 267 | ? String.IsNullOrEmpty(maskPath) 268 | ? await openAi.Images.Edit(imagePrompt, File.ReadAllBytes(imagePath), o => 269 | { 270 | o.N = imageCount; 271 | o.Size = imageSize; 272 | }) 273 | : await openAi.Images.Edit(imagePrompt, File.ReadAllBytes(imagePath), File.ReadAllBytes(maskPath), o => 274 | { 275 | o.N = imageCount; 276 | o.Size = imageSize; 277 | }) 278 | : String.IsNullOrEmpty(maskPath) 279 | ? await openAi.Images.Edit(imagePrompt, imagePath, o => 280 | { 281 | o.N = imageCount; 282 | o.Size = imageSize; 283 | }) 284 | : await openAi.Images.Edit(imagePrompt, imagePath, maskPath, o => 285 | { 286 | o.N = imageCount; 287 | o.Size = imageSize; 288 | }); 289 | 290 | if (response.IsSuccess) 291 | { 292 | List Results = response.Result!.Data.Select(i => i.Url).ToList(); 293 | 294 | // Send the URL(s) of the image(s) back to the OpenAI VoiceAttack Plugin 295 | Piping.SendArgsToNamedPipe(Results.ToArray()); 296 | } 297 | else 298 | { 299 | Console.WriteLine($"{response.ErrorMessage}"); 300 | 301 | // Send the error message back to the OpenAI VoiceAttack Plugin 302 | throw new Exception($"Image Editing Error: {response.ErrorMessage}"); 303 | } 304 | } 305 | } 306 | catch (Exception ex) 307 | { 308 | Console.WriteLine(ex.ToString()); 309 | 310 | // Send the error message back to the OpenAI VoiceAttack Plugin 311 | throw new Exception($"Dall-E Exception: {ex.Message}"); 312 | } 313 | } 314 | 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /OpenAI_NET/ico/voiceattack.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SemlerPDX/OpenAI-VoiceAttack-Plugin/92efbb1cb78b19a0f2204f7147242cdb743ecdc9/OpenAI_NET/ico/voiceattack.ico -------------------------------------------------------------------------------- /OpenAI_NET/service/Logging.cs: -------------------------------------------------------------------------------- 1 | namespace OpenAI_NET.service 2 | { 3 | /// 4 | /// A class for methods providing a means to log data to an external log file. 5 | /// 6 | /// 7 | ///
OpenAI API VoiceAttack Plugin
8 | ///
Copyright (C) 2023 Aaron Semler
9 | ///
github.com/SemlerPDX
10 | ///
veterans-gaming.com/semlerpdx-avcs
11 | ///

12 | /// This program is free software: you can redistribute it and/or modify
13 | /// it under the terms of the GNU General Public License as published by
14 | /// the Free Software Foundation, either version 3 of the License, or
15 | /// (at your option) any later version.
16 | ///
17 | /// This program is distributed in the hope that it will be useful,
18 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | /// GNU General Public License for more details.
21 | ///
22 | /// You should have received a copy of the GNU General Public License
23 | /// along with this program. If not, see gnu.org/licenses. 24 | ///
25 | public static class Logging 26 | { 27 | private static readonly long LOG_MAX_BYTES = 104857600L; // 100 MB max log size 28 | private static readonly string DEFAULT_LOG_NAME = "openai_errors"; 29 | private static readonly string DEFAULT_LOG_FOLDER = Path.Combine( 30 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 31 | "OpenAI_VoiceAttack_Plugin"); 32 | private static string ERROR_LOG_PATH { get; set; } = Path.Combine( 33 | Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments), 34 | DEFAULT_LOG_NAME); 35 | 36 | /// 37 | /// A method to set the path for error logging during this session. 38 | /// 39 | public static void SetErrorLogPath() 40 | { 41 | try 42 | { 43 | // Use the default AppData Roaming folder with a sub-folder if an exception occurs 44 | if (Directory.Exists(DEFAULT_LOG_FOLDER)) 45 | { 46 | ERROR_LOG_PATH = Path.Combine(DEFAULT_LOG_FOLDER, DEFAULT_LOG_NAME + ".log"); 47 | } 48 | } 49 | catch (Exception ex) 50 | { 51 | Console.WriteLine($"OpenAI_NET Error: {ex.Message}"); 52 | } 53 | } 54 | 55 | private static bool CheckAndRotateErrorsLog(string path) 56 | { 57 | try 58 | { 59 | if (File.Exists(path)) 60 | { 61 | FileInfo? file = new(path); 62 | if (file.Length > LOG_MAX_BYTES) 63 | { 64 | string timestamp = DateTime.Now.ToString("MMddyyyyHHmmss"); 65 | string newFilePath = System.IO.Path.Combine(DEFAULT_LOG_FOLDER, DEFAULT_LOG_NAME + "_" + timestamp + ".log"); 66 | File.Copy(path, newFilePath); 67 | File.Delete(path); 68 | } 69 | file = null; 70 | } 71 | return true; 72 | } 73 | catch (Exception ex) 74 | { 75 | Console.WriteLine($"OpenAI_NET Error: Error rotating oversized errors log: {ex.Message}"); 76 | return false; 77 | } 78 | } 79 | 80 | /// 81 | /// A method for logging error messages to file. 82 | /// 83 | /// The message to be appended to the log file. 84 | public static void WriteToLogFile(string logMessage) 85 | { 86 | try 87 | { 88 | if (CheckAndRotateErrorsLog(ERROR_LOG_PATH)) 89 | { 90 | using StreamWriter writer = new(ERROR_LOG_PATH, true); 91 | writer.WriteLine($"OpenAI_NET Error at {DateTime.Now}:"); 92 | writer.WriteLine(logMessage); 93 | writer.WriteLine("=========================================================================="); 94 | writer.WriteLine(string.Empty); 95 | } 96 | else 97 | { 98 | throw new Exception("OpenAI_NET.Logging.CheckAndRotateErrorsLog() returned false"); 99 | } 100 | } 101 | catch (Exception ex) 102 | { 103 | Console.WriteLine($"OpenAI_NET Error: unable to write to errors log file! Log Message: {logMessage} Failure Reason:{ex.Message}"); 104 | } 105 | } 106 | 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /OpenAI_NET/service/Piping.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Pipes; 2 | 3 | namespace OpenAI_NET.service 4 | { 5 | /// 6 | /// A class providing methods for interprocess communications 7 | /// between this application and the OpenAI VoiceAttack Plugin. 8 | /// 9 | /// 10 | ///
OpenAI_NET App for the OpenAI API VoiceAttack Plugin
11 | ///
Copyright (C) 2023 Aaron Semler
12 | ///
github.com/SemlerPDX
13 | ///
veterans-gaming.com/semlerpdx-avcs
14 | ///

15 | /// This program is free software: you can redistribute it and/or modify
16 | /// it under the terms of the GNU General Public License as published by
17 | /// the Free Software Foundation, either version 3 of the License, or
18 | /// (at your option) any later version.
19 | ///
20 | /// This program is distributed in the hope that it will be useful,
21 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | /// GNU General Public License for more details.
24 | ///
25 | /// You should have received a copy of the GNU General Public License
26 | /// along with this program. If not, see gnu.org/licenses. 27 | ///
28 | public static class Piping 29 | { 30 | private static readonly string PipeNameTHIS = "OpenAI_NET"; 31 | private static readonly string PipeNameIN = "OpenAI_NET_Pipe"; 32 | private static readonly string PipeNameOUT = "OpenAI_Plugin_Pipe"; 33 | 34 | /// 35 | /// A method to pipe a function response to the OpenAI VoiceAttack Plugin. 36 | /// 37 | /// A string array containing the response of a function request. 38 | /// True upon success, false if otherwise. 39 | /// Thrown when an error occurs in WriteLine(). 40 | /// Thrown when an error occurs in WriteLine(). 41 | /// Thrown when an error occurs in Connect(). 42 | public static bool SendArgsToNamedPipe(string[] args) 43 | { 44 | if (args == null || args.Length == 0) 45 | { 46 | throw new ArgumentException($"The {PipeNameTHIS} arguments array must contain at least one element.", nameof(args)); 47 | } 48 | 49 | try 50 | { 51 | // Send the return args over the named pipe back to the OpenAI VoiceAttack Plugin 52 | using NamedPipeClientStream pipeClient = new(".", PipeNameOUT, PipeDirection.Out); 53 | if (!pipeClient.IsConnected) 54 | pipeClient.Connect(); 55 | 56 | using StreamWriter writer = new(pipeClient); 57 | foreach (string arg in args) 58 | { 59 | if (!String.IsNullOrEmpty(arg)) 60 | writer.WriteLine(arg); 61 | } 62 | } 63 | catch (Exception ex) 64 | { 65 | Console.WriteLine($"{PipeNameTHIS} Error: SendArgsToNamedPipe Exception occurred: {ex.Message}"); 66 | } 67 | return true; 68 | } 69 | 70 | /// 71 | /// A method to listen for piped function requests from the OpenAI VoiceAttack Plugin. 72 | /// 73 | /// A string array starting with the desired function, followed by the OpenAI API Key, 74 | /// then any required parameters (see documentation). 75 | /// Thrown when an error occurs in ReadLine(). 76 | /// Thrown when an error occurs in WaitForConnection() or ReadLine(). 77 | /// Thrown when an error occurs in WaitForConnection() or Disconnect(). 78 | /// Thrown when an error occurs in WaitForConnection() or Disconnect(). 79 | public static string[] ListenForArgsOnNamedPipe() 80 | { 81 | List args = new(); 82 | try 83 | { 84 | // Listen for function args over the named pipe from the OpenAI VoiceAttack Plugin 85 | using NamedPipeServerStream pipeServer = new(PipeNameIN, PipeDirection.In, NamedPipeServerStream.MaxAllowedServerInstances); 86 | pipeServer.WaitForConnection(); 87 | 88 | using (StreamReader reader = new(pipeServer)) 89 | { 90 | string? line; 91 | while ((line = reader.ReadLine()) != null && !String.IsNullOrEmpty(line)) 92 | { 93 | args.Add(line); 94 | } 95 | } 96 | pipeServer.Dispose(); 97 | } 98 | catch (Exception ex) 99 | { 100 | Console.WriteLine($"{PipeNameTHIS} Error: ListenForArgsOnNamedPipe Exception occurred: {ex.Message}"); 101 | } 102 | 103 | return args.ToArray(); 104 | } 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33530.505 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAI_VoiceAttack_Plugin", "OpenAI_VoiceAttack_Plugin\OpenAI_VoiceAttack_Plugin.csproj", "{61846F70-45FB-40E0-86C3-8243054176F6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAI_NET", "OpenAI_NET\OpenAI_NET.csproj", "{3FEB52E2-0D47-495E-B91D-97794D01B7CF}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C00B2BF4-401F-41E4-AAE2-504EDAF1E832}" 11 | ProjectSection(SolutionItems) = preProject 12 | Directory.Build.props = Directory.Build.props 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {61846F70-45FB-40E0-86C3-8243054176F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {61846F70-45FB-40E0-86C3-8243054176F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {61846F70-45FB-40E0-86C3-8243054176F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {61846F70-45FB-40E0-86C3-8243054176F6}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {3FEB52E2-0D47-495E-B91D-97794D01B7CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {3FEB52E2-0D47-495E-B91D-97794D01B7CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {3FEB52E2-0D47-495E-B91D-97794D01B7CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {3FEB52E2-0D47-495E-B91D-97794D01B7CF}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {C2ED0264-BDF5-415F-B091-EF9EEE480CB9} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "ManualResetEventSlim instance is being reset after waiting, so it should not be marked as readonly -SemlerPDX Apr2023", Scope = "member", Target = "~F:OpenAI_VoiceAttack_Plugin.OpenAIplugin._asyncOperationCompleted")] 9 | [assembly: SuppressMessage("Style", "IDE0017:Simplify object initialization", Justification = "Personal Preference for forms design, one line per parameter including the form.Text -SemlerPDX Apr2023", Scope = "member", Target = "~M:OpenAI_VoiceAttack_Plugin.KeyForm.ShowKeyInputForm")] 10 | [assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "vaProxy method parameters unknown to IDE prior to runtime", Scope = "member", Target = "~M:OpenAI_VoiceAttack_Plugin.OpenAIplugin.VA_Exit1(System.Object)")] 11 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/OpenAI_VoiceAttack_Plugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {61846F70-45FB-40E0-86C3-8243054176F6} 8 | Library 9 | Properties 10 | OpenAI_VoiceAttack_Plugin 11 | OpenAI_VoiceAttack_Plugin 12 | v4.8 13 | 512 14 | true 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | bin\Debug\OpenAI_VoiceAttack_Plugin.xml 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\OpenAI_VoiceAttack_Plugin.xml 34 | 35 | 36 | 37 | ..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll 38 | 39 | 40 | ..\packages\Microsoft.Extensions.DependencyInjection.7.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.dll 41 | 42 | 43 | ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll 44 | 45 | 46 | ..\packages\Microsoft.Extensions.Http.7.0.0\lib\net462\Microsoft.Extensions.Http.dll 47 | 48 | 49 | ..\packages\Microsoft.Extensions.Logging.7.0.0\lib\net462\Microsoft.Extensions.Logging.dll 50 | 51 | 52 | ..\packages\Microsoft.Extensions.Logging.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll 53 | 54 | 55 | ..\packages\Microsoft.Extensions.Options.7.0.1\lib\net462\Microsoft.Extensions.Options.dll 56 | 57 | 58 | ..\packages\Microsoft.Extensions.Primitives.7.0.0\lib\net462\Microsoft.Extensions.Primitives.dll 59 | 60 | 61 | ..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll 62 | 63 | 64 | ..\packages\NAudio.2.1.0\lib\net472\NAudio.dll 65 | 66 | 67 | ..\packages\NAudio.Asio.2.1.0\lib\netstandard2.0\NAudio.Asio.dll 68 | 69 | 70 | ..\packages\NAudio.Core.2.1.0\lib\netstandard2.0\NAudio.Core.dll 71 | 72 | 73 | ..\packages\NAudio.Midi.2.1.0\lib\netstandard2.0\NAudio.Midi.dll 74 | 75 | 76 | ..\packages\NAudio.Wasapi.2.1.0\lib\netstandard2.0\NAudio.Wasapi.dll 77 | 78 | 79 | ..\packages\NAudio.WinForms.2.1.0\lib\net472\NAudio.WinForms.dll 80 | 81 | 82 | ..\packages\NAudio.WinMM.2.1.0\lib\netstandard2.0\NAudio.WinMM.dll 83 | 84 | 85 | ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll 86 | 87 | 88 | ..\packages\OpenAI.1.7.2\lib\netstandard2.0\OpenAI_API.dll 89 | 90 | 91 | 92 | 93 | ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll 94 | 95 | 96 | 97 | 98 | ..\packages\System.Diagnostics.DiagnosticSource.7.0.2\lib\net462\System.Diagnostics.DiagnosticSource.dll 99 | 100 | 101 | 102 | ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll 103 | 104 | 105 | 106 | ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll 107 | 108 | 109 | ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll 110 | 111 | 112 | ..\packages\System.Security.AccessControl.6.0.0\lib\net461\System.Security.AccessControl.dll 113 | 114 | 115 | ..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll 116 | 117 | 118 | 119 | ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll 120 | 121 | 122 | ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("OpenAI_VoiceAttack_Plugin")] 8 | [assembly: AssemblyDescription("OpenAI VoiceAttack Plugin")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("VG Labs")] 11 | [assembly: AssemblyProduct("OpenAI_VoiceAttack_Plugin")] 12 | [assembly: AssemblyCopyright("Copyright © 2023 Aaron Semler")] 13 | [assembly: AssemblyTrademark("by SemlerPDX")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("61846f70-45fb-40e0-86c3-8243054176f6")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.1.0.0")] 35 | [assembly: AssemblyFileVersion("1.1.0.0")] 36 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/appendix/All-VoiceAttack-Variables.txt: -------------------------------------------------------------------------------- 1 | Boolean("OpenAI_ChatWaiting#") 2 | Boolean("OpenAI_Chatting#") 3 | Boolean("OpenAI_ContentFlagged") 4 | Boolean("OpenAI_Debugging#") 5 | Boolean("OpenAI_DisableListenMode") 6 | Boolean("OpenAI_Error") 7 | Boolean("OpenAI_Listening#") 8 | Boolean("OpenAI_LogChat") 9 | Boolean("OpenAI_Loop_PreProcess") 10 | Boolean("OpenAI_ExternalContinue") 11 | Boolean("OpenAI_ExternalContinue_Unstoppable") 12 | Boolean("OpenAI_ExternalContinue_Exclusive") 13 | Boolean("OpenAI_ExternalResponder") 14 | Boolean("OpenAI_Plugin_Initialized") 15 | Boolean("OpenAI_SpeakChat") 16 | Decimal("OpenAI_FrequencyPenalty") 17 | Decimal("OpenAI_PresencePenalty") 18 | Decimal("OpenAI_Temperature") 19 | Decimal("OpenAI_TopP") 20 | Int("OpenAI_ListenTimeout_Seconds") 21 | Int("OpenAI_ListenTimeout_InitialSeconds") 22 | Int("OpenAI_NumChoicesPerMessage") 23 | Int("OpenAI_MaxTokens") 24 | Text("OpenAI_API_Key") 25 | Text("OpenAI_API_Key_FileName") 26 | Text("OpenAI_API_Key_FileFolder") 27 | Text("OpenAI_API_Org") 28 | Text("OpenAI_AudioFile") 29 | Text("OpenAI_AudioPath") 30 | Text("OpenAI_Command_ExternalResponder") 31 | Text("OpenAI_Context") 32 | Text("OpenAI_Command_DictationStop") 33 | Text("OpenAI_EmbeddingInput") 34 | Text("OpenAI_EmbeddingResponse") 35 | Text("OpenAI_FileName") 36 | Text("OpenAI_FilePath") 37 | Text("OpenAI_FilePurpose") 38 | Text("OpenAI_ImageCount") 39 | Text("OpenAI_ImageMaskPath") 40 | Text("OpenAI_ImagePath") 41 | Text("OpenAI_ImagePrompt") 42 | Text("OpenAI_ImageSize") 43 | Text("OpenAI_Command_DictationStart") 44 | Text("OpenAI_LogChat_InputPretext") 45 | Text("OpenAI_LogChat_OutputPretext") 46 | Text("OpenAI_Model") 47 | Text("OpenAI_Plugin_Version") 48 | Text("OpenAI_Plugin_ConfigFolderPath") 49 | Text("OpenAI_Plugin_ErrorsLogPath") 50 | Text("OpenAI_Plugin_WhisperFolderPath") 51 | Text("OpenAI_Response") 52 | Text("OpenAI_ResponseCode") 53 | Text("OpenAI_ResponseLinks") 54 | Text("OpenAI_TTS_Response") 55 | Text("OpenAI_Command_Speech") 56 | Text("OpenAI_StopSequences") 57 | Text("OpenAI_SystemPrompt") 58 | Text("OpenAI_UserInput") 59 | Text("OpenAI_UserInput_Last") 60 | Text("OpenAI_UserInput_Example") 61 | Text("OpenAI_ChatbotOutput_Example") 62 | Text("OpenAI_UserInput_Example1") 63 | Text("OpenAI_ChatbotOutput_Example1") 64 | Text("OpenAI_TTS_PreListen") 65 | Text("OpenAI_TTS_PreProcess") 66 | Text("OpenAI_TTS_ContentFlagged") 67 | Text("OpenAI_TTS_DeleteFailure") 68 | Text("OpenAI_TTS_DeleteSuccess") 69 | Text("OpenAI_TTS_ListFailure") 70 | Text("OpenAI_TTS_ListSuccess") 71 | Text("OpenAI_TTS_UploadFailure") 72 | Text("OpenAI_TTS_UploadSuccess") -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/appendix/history/chatgpt_response.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SemlerPDX/OpenAI-VoiceAttack-Plugin/92efbb1cb78b19a0f2204f7147242cdb743ecdc9/OpenAI_VoiceAttack_Plugin/appendix/history/chatgpt_response.txt -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/appendix/images/openai_plugin_dalle_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SemlerPDX/OpenAI-VoiceAttack-Plugin/92efbb1cb78b19a0f2204f7147242cdb743ecdc9/OpenAI_VoiceAttack_Plugin/appendix/images/openai_plugin_dalle_example.png -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/appendix/images/openai_plugin_mask_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SemlerPDX/OpenAI-VoiceAttack-Plugin/92efbb1cb78b19a0f2204f7147242cdb743ecdc9/OpenAI_VoiceAttack_Plugin/appendix/images/openai_plugin_mask_example.png -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/appendix/systems/SystemPrompt_ChatGPT_TTS.txt: -------------------------------------------------------------------------------- 1 | As ChatGPT in TTS mode, I'll provide brief and accurate responses to your input suitable for text-to-speech use. I'll round any decimals to one place for simplicity. To ensure a faster response, I'll limit the length of my answers, but I'll try to be as complete as possible. I won't provide code examples or any other formatted text that won't work well with text-to-speech. -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/appendix/tuning/fine-tuning-template.JSON: -------------------------------------------------------------------------------- 1 | {"model":"davinci","prompt":"What is the meaning of life?\n\n###\n\n","completion":" The meaning of life is to seek happiness and fulfillment.###"} 2 | {"model":"davinci","prompt":"What is the meaning of life?\n\n###\n\n","completion":" Happiness and fulfillment are the ultimate goals of human existence.###"} 3 | {"model":"davinci","prompt":"What is the meaning of life?\n\n###\n\n","completion":" Life has no inherent meaning or purpose.###"} 4 | {"model":"davinci","prompt":"What is the meaning of life?\n\n###\n\n","completion":" We must create our own meaning and purpose in life.###"} 5 | {"model":"davinci","prompt":"What is the meaning of life?\n\n###\n\n","completion":" The meaning of life is to love and be loved.###"} 6 | {"model":"davinci","prompt":"What is the meaning of life?\n\n###\n\n","completion":" Love is the most important thing in life.###"} -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/context/Completion.cs: -------------------------------------------------------------------------------- 1 | using OpenAI_API; 2 | using OpenAI_API.Completions; 3 | using OpenAI_API.Models; 4 | using System; 5 | using System.Threading.Tasks; 6 | 7 | namespace OpenAI_VoiceAttack_Plugin 8 | { 9 | /// 10 | /// A class providing a method for sending completion requests to the OpenAI API using the library. 11 | /// 12 | /// 13 | ///
OpenAI API VoiceAttack Plugin
14 | ///
Copyright (C) 2023 Aaron Semler
15 | ///
github.com/SemlerPDX
16 | ///
veterans-gaming.com/semlerpdx-avcs
17 | ///

18 | /// This program is free software: you can redistribute it and/or modify
19 | /// it under the terms of the GNU General Public License as published by
20 | /// the Free Software Foundation, either version 3 of the License, or
21 | /// (at your option) any later version.
22 | ///
23 | /// This program is distributed in the hope that it will be useful,
24 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | /// GNU General Public License for more details.
27 | ///
28 | /// You should have received a copy of the GNU General Public License
29 | /// along with this program. If not, see gnu.org/licenses. 30 | ///
31 | public static class Completion 32 | { 33 | /// 34 | /// Send a text completion request to the OpenAI API using the supplied input, 35 | /// and return the completed text, using the specified parameters (optional). 36 | /// 37 | public static async Task CompleteText() 38 | { 39 | try 40 | { 41 | string userInput = OpenAIplugin.VA_Proxy.GetText("OpenAI_UserInput") ?? String.Empty; 42 | string response = String.Empty; 43 | 44 | if (String.IsNullOrEmpty(userInput)) { throw new Exception("User prompt in OpenAI_UserInput text variable is null or empty."); } 45 | 46 | OpenAIplugin.VA_Proxy.SetText("OpenAI_UserInput_Last", userInput); 47 | 48 | // Set the OpenAI GPT Model and any/all user options and create a new Completion request: 49 | Model userModel = ModelsGPT.GetOpenAI_Model(true); 50 | 51 | // Ensure max tokens in 'OpenAI_MaxTokens' is within range for the selected model, uses 512 if not set/invalid 52 | int userMaxTokens = ModelsGPT.GetValidMaxTokens(userModel); 53 | 54 | decimal getTemp = OpenAIplugin.VA_Proxy.GetDecimal("OpenAI_Temperature") ?? 0.2M; 55 | decimal getTopP = OpenAIplugin.VA_Proxy.GetDecimal("OpenAI_TopP") ?? 0M; 56 | decimal getFreqP = OpenAIplugin.VA_Proxy.GetDecimal("OpenAI_FrequencyPenalty") ?? 0M; 57 | decimal getPresP = OpenAIplugin.VA_Proxy.GetDecimal("OpenAI_PresencePenalty") ?? 0M; 58 | 59 | string getStopSequences = OpenAIplugin.VA_Proxy.GetText("OpenAI_StopSequences") ?? String.Empty; 60 | string[] userStopSequences = null; 61 | if (!string.IsNullOrEmpty(getStopSequences)) 62 | { 63 | userStopSequences = getStopSequences.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); 64 | } 65 | 66 | int? userNumChoices = OpenAIplugin.VA_Proxy.GetInt("OpenAI_NumChoicesPerMessage") ?? 1; 67 | double userTemperature = (double)getTemp; 68 | double userTopP = (double)getTopP; 69 | double userFrequencyPenalty = (double)getFreqP; 70 | double userPresencePenalty = (double)getPresP; 71 | 72 | OpenAIAPI api = OpenAI_Key.LOAD_KEY 73 | ? new OpenAIAPI(new APIAuthentication(OpenAI_Key.API_KEY, OpenAI_Key.API_ORG)) 74 | : new OpenAIAPI(APIAuthentication.LoadFromPath( 75 | directory: OpenAI_Key.DEFAULT_KEY_FILEFOLDER, 76 | filename: OpenAI_Key.DEFAULT_KEY_FILENAME, 77 | searchUp: true 78 | )); 79 | 80 | var result = await api.Completions.CreateCompletionAsync(new CompletionRequest( 81 | userInput, 82 | model: userModel, 83 | temperature: userTemperature, 84 | max_tokens: userMaxTokens, 85 | top_p: userTopP, 86 | numOutputs: userNumChoices, 87 | frequencyPenalty: userFrequencyPenalty, 88 | presencePenalty: userPresencePenalty, 89 | stopSequences: userStopSequences 90 | )); 91 | 92 | if (result != null && !String.IsNullOrEmpty(result.Completions.ToString())) 93 | { 94 | //response = result.Completions.ToString(); 95 | response = result.Completions[0].ToString(); 96 | } 97 | 98 | if (!String.IsNullOrEmpty(response)) 99 | { 100 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", response); 101 | } 102 | } 103 | catch (Exception ex) 104 | { 105 | throw new Exception($"OpenAI Plugin Error: {ex.Message}"); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/context/DallE.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace OpenAI_VoiceAttack_Plugin 6 | { 7 | /// 8 | /// A class with methods for handling image requests using OpenAI by sending and receiving 9 | /// data through the OpenAI_NET App which provides access to its Dall-E (.NET Core) library functions. 10 | /// 11 | /// 12 | ///
OpenAI API VoiceAttack Plugin
13 | ///
Copyright (C) 2023 Aaron Semler
14 | ///
github.com/SemlerPDX
15 | ///
veterans-gaming.com/semlerpdx-avcs
16 | ///

17 | /// This program is free software: you can redistribute it and/or modify
18 | /// it under the terms of the GNU General Public License as published by
19 | /// the Free Software Foundation, either version 3 of the License, or
20 | /// (at your option) any later version.
21 | ///
22 | /// This program is distributed in the hope that it will be useful,
23 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | /// GNU General Public License for more details.
26 | ///
27 | /// You should have received a copy of the GNU General Public License
28 | /// along with this program. If not, see gnu.org/licenses. 29 | ///
30 | public static class DallE 31 | { 32 | /// 33 | /// Generate image(s) based on a supplied prompt, and any additional parameters. 34 | /// 35 | /// Sets the OpenAI_Response text variable to a semicolon separated list of URLs to generated images. 36 | public static Task Generation() 37 | { 38 | try 39 | { 40 | string response = String.Empty; 41 | List args = new List 42 | { 43 | "image.generate", 44 | OpenAI_Key.API_KEY, 45 | OpenAIplugin.VA_Proxy.GetText("OpenAI_ImagePrompt") ?? String.Empty, 46 | OpenAIplugin.VA_Proxy.GetText("OpenAI_ImageCount") ?? String.Empty, 47 | OpenAIplugin.VA_Proxy.GetText("OpenAI_ImageSize") ?? String.Empty 48 | }; 49 | 50 | // Send the function request to the OpenAI_NET App 51 | if (!Piping.SendArgsToNamedPipe(args.ToArray())) 52 | { 53 | throw new Exception($"Failed to send Dall-E Generation request through pipe!"); 54 | } 55 | 56 | // Listen for the response from the OpenAI_NET App 57 | string[] responses = Piping.ListenForArgsOnNamedPipe(); 58 | if (responses != null && !String.IsNullOrEmpty(responses[0]) && !responses[0].StartsWith("OpenAI_NET")) 59 | { 60 | response = String.Join(";", responses); 61 | } 62 | else 63 | { 64 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Error", true); 65 | } 66 | 67 | // Set the response to the VoiceAttack text variable and exit 68 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", response); 69 | } 70 | catch (Exception ex) 71 | { 72 | throw new Exception($"Dall-E Error: {ex.Message}"); 73 | } 74 | return Task.CompletedTask; 75 | } 76 | 77 | /// 78 | /// Upload an image from the supplied file path and create variation(s) 79 | /// based on a supplied prompt, and any additional parameters. 80 | /// 81 | /// Sets the OpenAI_Response text variable to a semicolon separated list of URLs to image variations. 82 | public static Task Variation() { return Variation(false); } 83 | /// 84 | /// Upload an image from the supplied file path and create variation(s) 85 | /// based on a supplied prompt, and any additional parameters. 86 | /// 87 | /// A boolean directing the method to upload the image using bytes. 88 | /// Sets the OpenAI_Response text variable to a semicolon separated list of URLs to image variations. 89 | public static Task Variation(bool useBytes) 90 | { 91 | try 92 | { 93 | string response = String.Empty; 94 | string imageFunction = useBytes ? "image.variation" : "image.variation.bytes"; 95 | 96 | string imagePath = OpenAIplugin.VA_Proxy.GetText("OpenAI_ImagePath") ?? String.Empty; 97 | 98 | if (String.IsNullOrEmpty(imagePath)) { throw new Exception("File path in OpenAI_ImagePath text variable is null or empty!"); } 99 | 100 | 101 | List args = new List 102 | { 103 | imageFunction, 104 | OpenAI_Key.API_KEY, 105 | imagePath, 106 | OpenAIplugin.VA_Proxy.GetText("OpenAI_ImageCount") ?? String.Empty, 107 | OpenAIplugin.VA_Proxy.GetText("OpenAI_ImageSize") ?? String.Empty 108 | }; 109 | 110 | // Send the function request to the OpenAI_NET App 111 | if (!Piping.SendArgsToNamedPipe(args.ToArray())) 112 | { 113 | throw new Exception($"Failed to send Dall-E Variation request through pipe!"); 114 | } 115 | 116 | // Listen for the response from the OpenAI_NET App 117 | string[] responses = Piping.ListenForArgsOnNamedPipe(); 118 | if (responses != null && !String.IsNullOrEmpty(responses[0]) && !responses[0].StartsWith("OpenAI_NET")) 119 | { 120 | response = String.Join(";", responses); 121 | } 122 | else 123 | { 124 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Error", true); 125 | } 126 | 127 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", response); 128 | } 129 | catch (Exception ex) 130 | { 131 | throw new Exception($"Dall-E Error: {ex.Message}"); 132 | } 133 | return Task.CompletedTask; 134 | } 135 | 136 | /// 137 | /// Upload an image from the supplied file path and create edits 138 | /// based on a supplied prompt, an optional mask file path, and any additional parameters. 139 | /// 140 | /// Sets the OpenAI_Response text variable to a semicolon separated list of URLs to image edits. 141 | public static Task Editing() { return Editing(false); } 142 | /// 143 | /// Upload an image from the supplied file path and create edits 144 | /// based on a supplied prompt, an optional mask file path, and any additional parameters. 145 | /// 146 | /// A boolean directing the method to upload the image using bytes. 147 | /// Sets the OpenAI_Response text variable to a semicolon separated list of URLs to image edits. 148 | public static Task Editing(bool useBytes) 149 | { 150 | try 151 | { 152 | string response = String.Empty; 153 | string imageFunction = useBytes ? "image.edit" : "image.edit.bytes"; 154 | 155 | string imagePrompt = OpenAIplugin.VA_Proxy.GetText("OpenAI_ImagePrompt") ?? String.Empty; 156 | string imagePath = OpenAIplugin.VA_Proxy.GetText("OpenAI_ImagePath") ?? String.Empty; 157 | 158 | if (String.IsNullOrEmpty(imagePrompt)) { throw new Exception("Editing instructions in OpenAI_ImagePrompt text variable is null or empty!"); } 159 | if (String.IsNullOrEmpty(imagePath)) { throw new Exception("File path in OpenAI_ImagePath text variable is null or empty!"); } 160 | 161 | List args = new List 162 | { 163 | imageFunction, 164 | OpenAI_Key.API_KEY, 165 | imagePrompt, 166 | imagePath, 167 | OpenAIplugin.VA_Proxy.GetText("OpenAI_ImageCount") ?? String.Empty, 168 | OpenAIplugin.VA_Proxy.GetText("OpenAI_ImageSize") ?? String.Empty, 169 | OpenAIplugin.VA_Proxy.GetText("OpenAI_ImageMaskPath") ?? String.Empty 170 | }; 171 | 172 | // Send the function request to the OpenAI_NET App 173 | if (!Piping.SendArgsToNamedPipe(args.ToArray())) 174 | { 175 | throw new Exception($"Failed to send Dall-E Editing request through pipe!"); 176 | } 177 | 178 | // Listen for the response from the OpenAI_NET App 179 | string[] responses = Piping.ListenForArgsOnNamedPipe(); 180 | if (responses != null && !String.IsNullOrEmpty(responses[0])) 181 | { 182 | response = String.Join(";", responses); 183 | } 184 | else 185 | { 186 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Error", true); 187 | } 188 | 189 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", response); 190 | } 191 | catch (Exception ex) 192 | { 193 | throw new Exception($"Dall-E Error: {ex.Message}"); 194 | } 195 | return Task.CompletedTask; 196 | } 197 | 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/context/Embedding.cs: -------------------------------------------------------------------------------- 1 | using OpenAI_API; 2 | using OpenAI_API.Embedding; 3 | using OpenAI_API.Models; 4 | using System; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace OpenAI_VoiceAttack_Plugin 9 | { 10 | /// 11 | /// A class providing a method for returning Embedding Request metadata using the OpenAI API via the library. 12 | /// 13 | /// 14 | ///
OpenAI API VoiceAttack Plugin
15 | ///
Copyright (C) 2023 Aaron Semler
16 | ///
github.com/SemlerPDX
17 | ///
veterans-gaming.com/semlerpdx-avcs
18 | ///

19 | /// This program is free software: you can redistribute it and/or modify
20 | /// it under the terms of the GNU General Public License as published by
21 | /// the Free Software Foundation, either version 3 of the License, or
22 | /// (at your option) any later version.
23 | ///
24 | /// This program is distributed in the hope that it will be useful,
25 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
26 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 | /// GNU General Public License for more details.
28 | ///
29 | /// You should have received a copy of the GNU General Public License
30 | /// along with this program. If not, see gnu.org/licenses. 31 | ///
32 | public static class Embedding 33 | { 34 | /// 35 | /// Send embedding request to OpenAI API to get a newline and "; " deliniated string containing returned metadata. 36 | /// 37 | /// The data will be stored in the 'OpenAI_Response' VoiceAttack text variable, empty on failure. 38 | public static async Task Embed() 39 | { 40 | try 41 | { 42 | string embeddingInput = OpenAIplugin.VA_Proxy.GetText("OpenAI_EmbeddingInput") ?? String.Empty; 43 | string response = String.Empty; 44 | 45 | if (String.IsNullOrEmpty(embeddingInput)) { throw new Exception("Embedding input in OpenAI_EmbeddingInput text variable is null or empty."); } 46 | 47 | OpenAIplugin.VA_Proxy.SetText("OpenAI_EmbeddingResponse", String.Empty); 48 | 49 | OpenAIAPI api = OpenAI_Key.LOAD_KEY 50 | ? new OpenAIAPI(new APIAuthentication(OpenAI_Key.API_KEY, OpenAI_Key.API_ORG)) 51 | : new OpenAIAPI(APIAuthentication.LoadFromPath( 52 | directory: OpenAI_Key.DEFAULT_KEY_FILEFOLDER, 53 | filename: OpenAI_Key.DEFAULT_KEY_FILENAME, 54 | searchUp: true 55 | )); 56 | 57 | 58 | var result = await api.Embeddings.CreateEmbeddingAsync(new EmbeddingRequest( 59 | model: Model.AdaTextEmbedding, 60 | embeddingInput 61 | )); 62 | 63 | if (result != null && result.Data != null && result.Data.Count > 0) 64 | { 65 | var embeddingList = result.Data; 66 | if (embeddingList != null && embeddingList.Any()) 67 | { 68 | var embeddingStrings = embeddingList.Select(data => string.Join("; ", data.Embedding)); 69 | response = string.Join(Environment.NewLine, embeddingStrings); 70 | } 71 | } 72 | 73 | // Return the metadata as a string for post processing in VoiceAttack 74 | OpenAIplugin.VA_Proxy.SetText("OpenAI_EmbeddingResponse", response); 75 | } 76 | catch (Exception ex) 77 | { 78 | throw new Exception($"OpenAI Plugin Error: {ex.Message}"); 79 | } 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/context/Files.cs: -------------------------------------------------------------------------------- 1 | using OpenAI_API; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace OpenAI_VoiceAttack_Plugin 8 | { 9 | /// 10 | /// A class providing methods to work with files in the OpenAI account 11 | /// belonging to the supplied API Key using the library. 12 | /// 13 | /// 14 | ///
OpenAI API VoiceAttack Plugin
15 | ///
Copyright (C) 2023 Aaron Semler
16 | ///
github.com/SemlerPDX
17 | ///
veterans-gaming.com/semlerpdx-avcs
18 | ///

19 | /// This program is free software: you can redistribute it and/or modify
20 | /// it under the terms of the GNU General Public License as published by
21 | /// the Free Software Foundation, either version 3 of the License, or
22 | /// (at your option) any later version.
23 | ///
24 | /// This program is distributed in the hope that it will be useful,
25 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
26 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 | /// GNU General Public License for more details.
28 | ///
29 | /// You should have received a copy of the GNU General Public License
30 | /// along with this program. If not, see gnu.org/licenses. 31 | ///
32 | public static class Files 33 | { 34 | /// 35 | /// Upload a file to the OpenAI account of the supplied API Key with an optional custom purpose, default is 'fine-tuning'. 36 | ///
Set the OpenAI_FilePurpose text variable with the intendend purpose of the uploaded documents. (see documentation) 37 | ///
Use "fine-tune" for Fine-tuning (default, if not set). 38 | /// This allows to validate the format of the uploaded file. 39 | ///
40 | /// Sets the OpenAI_Response text variable to the OpenAI API id of the uploaded file (if successful) or blank if otherwise, 41 | ///
and a success/failure message in the OpenAI_TTS_Response text variable.
42 | /// Thrown when an error occurs while getting files data using GetFilesAsync(). 43 | public static async Task Upload() 44 | { 45 | try 46 | { 47 | string filePath = OpenAIplugin.VA_Proxy.GetText("OpenAI_FilePath") ?? String.Empty; 48 | string filePurpose = OpenAIplugin.VA_Proxy.GetText("OpenAI_FilePurpose") ?? String.Empty; 49 | string successMessage = OpenAIplugin.VA_Proxy.GetText("OpenAI_TTS_UploadSuccess") ?? "The requested file has been uploaded."; 50 | string failureMessage = OpenAIplugin.VA_Proxy.GetText("OpenAI_TTS_UploadFailure") ?? "The requested file was not found, or an error has occurred while uploading."; 51 | 52 | if (String.IsNullOrEmpty(filePath)) { throw new Exception("File path in OpenAI_FilePath text variable is null or empty!"); } 53 | 54 | List filesList = new List(); 55 | string files = string.Empty; 56 | 57 | OpenAIAPI api = OpenAI_Key.LOAD_KEY 58 | ? new OpenAIAPI(new APIAuthentication(OpenAI_Key.API_KEY, OpenAI_Key.API_ORG)) 59 | : new OpenAIAPI(APIAuthentication.LoadFromPath( 60 | directory: OpenAI_Key.DEFAULT_KEY_FILEFOLDER, 61 | filename: OpenAI_Key.DEFAULT_KEY_FILENAME, 62 | searchUp: true 63 | )); 64 | 65 | var response = String.IsNullOrEmpty(filePurpose) 66 | ? await api.Files.UploadFileAsync(filePath) 67 | : await api.Files.UploadFileAsync(filePath, filePurpose); 68 | 69 | string message = response != null 70 | ? successMessage 71 | : failureMessage; 72 | 73 | OpenAIplugin.VA_Proxy.SetText("OpenAI_TTS_Response", message); 74 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", response.Id ?? String.Empty); 75 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Error", response == null); 76 | } 77 | catch (HttpRequestException ex) 78 | { 79 | throw new Exception($"OpenAI Plugin File Upload Error: {ex.Message}"); 80 | } 81 | catch (Exception ex) 82 | { 83 | throw new Exception($"OpenAI Plugin File Upload Error: {ex.Message}"); 84 | } 85 | } 86 | 87 | /// 88 | /// Get a list of all files uploaded to the OpenAI account of the supplied API Key. 89 | /// 90 | /// Sets the OpenAI_Response text variable to a string containing 91 | /// a semicolon separated list of files, or an empty string if none exist (or otherwise). 92 | /// Thrown when an error occurs while getting files data using GetFilesAsync(). 93 | public static async Task List() 94 | { 95 | try 96 | { 97 | List filesList = new List(); 98 | string files = String.Empty; 99 | string successMessage = OpenAIplugin.VA_Proxy.GetText("OpenAI_TTS_ListSuccess") ?? "The list of files has been assembled."; 100 | string failureMessage = OpenAIplugin.VA_Proxy.GetText("OpenAI_TTS_ListFailure") ?? "There are no files found in this account."; 101 | 102 | OpenAIAPI api = OpenAI_Key.LOAD_KEY 103 | ? new OpenAIAPI(new APIAuthentication(OpenAI_Key.API_KEY, OpenAI_Key.API_ORG)) 104 | : new OpenAIAPI(APIAuthentication.LoadFromPath( 105 | directory: OpenAI_Key.DEFAULT_KEY_FILEFOLDER, 106 | filename: OpenAI_Key.DEFAULT_KEY_FILENAME, 107 | searchUp: true 108 | )); 109 | 110 | var response = await api.Files.GetFilesAsync(); 111 | 112 | foreach (var file in response) 113 | { 114 | filesList.Add(file.Name); 115 | } 116 | 117 | if (filesList.Count > 0) 118 | { 119 | files = string.Join(";", filesList); 120 | } 121 | 122 | string message = !String.IsNullOrEmpty(files) 123 | ? successMessage 124 | : failureMessage; 125 | 126 | OpenAIplugin.VA_Proxy.SetText("OpenAI_TTS_Response", message); 127 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", files); 128 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Error", String.IsNullOrEmpty(files)); 129 | } 130 | catch (HttpRequestException ex) 131 | { 132 | throw new Exception($"OpenAI Plugin File List Error: {ex.Message}"); 133 | } 134 | catch (Exception ex) 135 | { 136 | throw new Exception($"OpenAI Plugin File List Error: {ex.Message}"); 137 | } 138 | } 139 | 140 | /// 141 | /// Delete a file from the OpenAI account of the supplied API Key 142 | /// which matches the supplied file name. 143 | /// 144 | /// Sets the OpenAI_Response text variable to the OpenAI API id of the deleted file (if successful) or blank if otherwise, 145 | ///
and a success/failure message in the OpenAI_TTS_Response text variable.
146 | /// Thrown when an error occurs while getting files data using GetFilesAsync(). 147 | public static async Task Delete() 148 | { 149 | try 150 | { 151 | string successMessage = OpenAIplugin.VA_Proxy.GetText("OpenAI_TTS_DeleteSuccess") ?? "The requested file has been deleted."; 152 | string failureMessage = OpenAIplugin.VA_Proxy.GetText("OpenAI_TTS_DeleteFailure") ?? "The requested file was not found, or an error has occurred."; 153 | string message = failureMessage; 154 | string fileName = OpenAIplugin.VA_Proxy.GetText("OpenAI_FileName") ?? String.Empty; 155 | 156 | if (String.IsNullOrEmpty(fileName)) { throw new Exception("File name in OpenAI_FileName text variable is null or empty!"); } 157 | 158 | OpenAIAPI api = OpenAI_Key.LOAD_KEY 159 | ? new OpenAIAPI(new APIAuthentication(OpenAI_Key.API_KEY, OpenAI_Key.API_ORG)) 160 | : new OpenAIAPI(APIAuthentication.LoadFromPath( 161 | directory: OpenAI_Key.DEFAULT_KEY_FILEFOLDER, 162 | filename: OpenAI_Key.DEFAULT_KEY_FILENAME, 163 | searchUp: true 164 | )); 165 | 166 | var response = await api.Files.GetFilesAsync(); 167 | 168 | foreach (var file in response) 169 | { 170 | if (file.Name == fileName) 171 | { 172 | var deleteResponse = await api.Files.DeleteFileAsync(file.Id); 173 | if (deleteResponse.Deleted) 174 | { 175 | message = successMessage; 176 | } 177 | } 178 | } 179 | 180 | OpenAIplugin.VA_Proxy.SetText("OpenAI_TTS_Response", message); 181 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", fileName); 182 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Error", message == failureMessage); 183 | } 184 | catch (HttpRequestException ex) 185 | { 186 | throw new Exception($"OpenAI Plugin File Delete Error: {ex.Message}"); 187 | } 188 | catch (Exception ex) 189 | { 190 | throw new Exception($"OpenAI Plugin File Delete Error: {ex.Message}"); 191 | } 192 | } 193 | 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/context/KeyForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | using System.Windows.Forms; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace OpenAI_VoiceAttack_Plugin 8 | { 9 | /// 10 | /// A class to provide a method for users to supply their OpenAI API Key for the plugin. 11 | /// 12 | /// 13 | ///
OpenAI API VoiceAttack Plugin
14 | ///
Copyright (C) 2023 Aaron Semler
15 | ///
github.com/SemlerPDX
16 | ///
veterans-gaming.com/semlerpdx-avcs
17 | ///

18 | /// This program is free software: you can redistribute it and/or modify
19 | /// it under the terms of the GNU General Public License as published by
20 | /// the Free Software Foundation, either version 3 of the License, or
21 | /// (at your option) any later version.
22 | ///
23 | /// This program is distributed in the hope that it will be useful,
24 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | /// GNU General Public License for more details.
27 | ///
28 | /// You should have received a copy of the GNU General Public License
29 | /// along with this program. If not, see gnu.org/licenses. 30 | ///
31 | public class KeyForm 32 | { 33 | private static readonly string OpenAiWebsite = "https://platform.openai.com/account/api-keys"; 34 | private static readonly string OpenAiPage = "platform.openai.com/account/api-keys"; 35 | private static readonly string ExistingKeyPhrase = "(enter a new key to overwrite key already on file)"; 36 | 37 | private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); 38 | private const UInt32 SWP_NOSIZE = 0x0001; 39 | private const UInt32 SWP_NOMOVE = 0x0002; 40 | private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE; 41 | 42 | [DllImport("user32.dll")] 43 | [return: MarshalAs(UnmanagedType.Bool)] 44 | private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); 45 | 46 | private static void Footer_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 47 | { 48 | try 49 | { 50 | System.Diagnostics.Process.Start(e.Link.LinkData.ToString()); 51 | } 52 | catch 53 | { 54 | // ...log message, ignore, and continue 55 | Logging.WriteToLog_Long("OpenAI Plugin Error: was unable to open link to OpenAI website!", "red"); 56 | } 57 | } 58 | 59 | 60 | /// 61 | /// Open a bog-standard dialog box to let users enter their OpenAI API Key and save it to their config folder. 62 | /// 63 | public void ShowKeyInputForm() 64 | { 65 | if (OpenAIplugin.OpenAI_KeyFormOpen) 66 | { 67 | Logging.WriteToLog_Long("OpenAI Plugin Message: The Key Menu is already open!", "orange"); 68 | return; 69 | } 70 | 71 | try 72 | { 73 | // Alternate save method - if API Key is provided before calling this context, save to file and exit form: 74 | string extKey = OpenAIplugin.VA_Proxy.GetText("OpenAI_API_Key") ?? String.Empty; 75 | if (!String.IsNullOrEmpty(extKey)) 76 | { 77 | extKey = extKey.Trim(); 78 | OpenAI_Key.API_KEY = extKey; 79 | if (OpenAI_Key.SaveToFile(extKey)) 80 | { 81 | Logging.WriteToLog_Long("OpenAI Plugin: Success! Your OpenAI API Key has been saved!", "green"); 82 | OpenAIplugin.VA_Proxy.SetText("OpenAI_API_Key", null); 83 | return; 84 | } 85 | } 86 | 87 | OpenAIplugin.OpenAI_KeyFormOpen = true; 88 | using (Form keyInputForm = new Form()) 89 | { 90 | // Get the existing key (if any) 91 | string key = OpenAI_Key.LoadFromFile(); 92 | keyInputForm.FormBorderStyle = FormBorderStyle.FixedSingle; 93 | keyInputForm.MaximizeBox = false; 94 | keyInputForm.MinimizeBox = false; 95 | 96 | // Set the window in center screen and to be always on top until closed 97 | keyInputForm.StartPosition = FormStartPosition.CenterScreen; 98 | keyInputForm.TopMost = true; 99 | 100 | // Form title text 101 | keyInputForm.Text = OpenAIplugin.VA_DisplayName().Replace("API", "Plugin"); 102 | 103 | // Set form background color 104 | keyInputForm.BackColor = Color.FromArgb(123, 123, 123); 105 | 106 | 107 | // Form icon - voiceattack icon will look pretty and professional 108 | string iconPath = System.IO.Path.Combine(OpenAIplugin.VA_Proxy.InstallDir, "voiceattack.ico") ?? String.Empty; 109 | if (!string.IsNullOrEmpty(iconPath) && File.Exists(iconPath)) 110 | { 111 | keyInputForm.Icon = new Icon(iconPath); 112 | } 113 | keyInputForm.MinimumSize = new Size(400, keyInputForm.MinimumSize.Height); 114 | 115 | System.Windows.Forms.Label spacer = new System.Windows.Forms.Label 116 | { 117 | Text = "\n\n", 118 | Dock = DockStyle.Top 119 | }; 120 | 121 | System.Windows.Forms.Label label = new System.Windows.Forms.Label(); 122 | label.Text = "Enter your OpenAI API Key to use in this VoiceAttack Plugin:"; 123 | label.Font = new Font(label.Font, FontStyle.Bold); 124 | label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 125 | label.Dock = DockStyle.Top; 126 | 127 | System.Windows.Forms.TextBox textBox = new System.Windows.Forms.TextBox { UseSystemPasswordChar = true }; 128 | textBox.Text = !String.IsNullOrEmpty(key) ? ExistingKeyPhrase : OpenAI_Key.EXAMPLE_API_KEY; 129 | textBox.TextAlign = HorizontalAlignment.Center; 130 | textBox.Dock = DockStyle.Top; 131 | 132 | 133 | System.Windows.Forms.Button okButton = new System.Windows.Forms.Button(); 134 | okButton.Text = "SAVE"; 135 | okButton.Font = new Font(okButton.Font, FontStyle.Bold); 136 | okButton.Dock = DockStyle.Bottom; 137 | okButton.DialogResult = DialogResult.OK; 138 | okButton.FlatStyle = FlatStyle.Flat; 139 | okButton.BackColor = Color.FromArgb(107, 107, 107); 140 | okButton.FlatAppearance.BorderColor = Color.FromArgb(75, 75, 75); 141 | okButton.Margin = new Padding(0, 30, 0, 30); 142 | 143 | System.Windows.Forms.Button deleteButton = new System.Windows.Forms.Button(); 144 | deleteButton.Text = "DELETE"; 145 | deleteButton.Font = new Font(deleteButton.Font, FontStyle.Bold); 146 | deleteButton.Dock = DockStyle.Bottom; 147 | deleteButton.Location = new Point((keyInputForm.ClientSize.Width - okButton.Width) / 2, okButton.Location.Y); 148 | deleteButton.DialogResult = DialogResult.Yes; // using to indicate a delete request 149 | deleteButton.FlatStyle = FlatStyle.Flat; 150 | deleteButton.BackColor = Color.FromArgb(107, 107, 107); 151 | deleteButton.FlatAppearance.BorderColor = Color.FromArgb(75, 75, 75); 152 | deleteButton.Margin = new Padding(0, 30, 0, 30); 153 | 154 | System.Windows.Forms.Button cancelButton = new System.Windows.Forms.Button(); 155 | cancelButton.Text = "CANCEL"; 156 | cancelButton.Font = new Font(cancelButton.Font, FontStyle.Bold); 157 | cancelButton.Dock = DockStyle.Bottom; 158 | cancelButton.DialogResult = DialogResult.Cancel; // ignoring 159 | cancelButton.FlatStyle = FlatStyle.Flat; 160 | cancelButton.BackColor = Color.FromArgb(107, 107, 107); 161 | cancelButton.FlatAppearance.BorderColor = Color.FromArgb(75, 75, 75); 162 | cancelButton.Margin = new Padding(0, 30, 0, 30); 163 | 164 | System.Windows.Forms.Label note = new System.Windows.Forms.Label(); 165 | note.Text = "( you can cancel and return to this window anytime, say 'Open the Key Menu' )"; 166 | note.Font = new Font(note.Font, FontStyle.Italic); 167 | note.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 168 | note.Dock = DockStyle.Bottom; 169 | 170 | LinkLabel footer = new LinkLabel(); 171 | footer.Text = $" Find your key here: {OpenAiPage}"; 172 | footer.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 173 | footer.Links.Add(35, 36, OpenAiWebsite); 174 | footer.AutoSize = true; 175 | footer.UseMnemonic = false; 176 | footer.LinkClicked += new LinkLabelLinkClickedEventHandler(Footer_LinkClicked); 177 | footer.Dock = DockStyle.Bottom; 178 | 179 | 180 | keyInputForm.Controls.Add(textBox); 181 | keyInputForm.Controls.Add(okButton); 182 | keyInputForm.Controls.Add(deleteButton); 183 | keyInputForm.Controls.Add(cancelButton); 184 | keyInputForm.Controls.Add(note); 185 | keyInputForm.Controls.Add(footer); 186 | keyInputForm.Controls.Add(label); 187 | keyInputForm.Controls.Add(spacer); 188 | 189 | // Bring the form to the foreground as 'always on top' until closed 190 | SetWindowPos(keyInputForm.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); 191 | 192 | // Show the form and get the result 193 | DialogResult result = keyInputForm.ShowDialog(); 194 | 195 | if (result == DialogResult.OK) 196 | { 197 | key = textBox.Text; 198 | if (!String.IsNullOrEmpty(key) && key != OpenAI_Key.EXAMPLE_API_KEY && key != ExistingKeyPhrase) 199 | { 200 | key = key.Trim(); 201 | OpenAI_Key.API_KEY = key; 202 | if (OpenAI_Key.SaveToFile(key)) 203 | { 204 | Logging.WriteToLog_Long("OpenAI Plugin: Success! Your OpenAI API Key has been saved!", "green"); 205 | return; 206 | } 207 | } 208 | Logging.WriteToLog_Long("OpenAI Plugin: An error occurred trying to save your OpenAI API Key!", "red"); 209 | 210 | } 211 | else if (result == DialogResult.Yes) 212 | { 213 | if (OpenAI_Key.DeleteFromFile()) 214 | { 215 | Logging.WriteToLog_Long("OpenAI Plugin: Success! Your OpenAI API Key has been deleted!", "green"); 216 | } 217 | else 218 | { 219 | Logging.WriteToLog_Long("OpenAI Plugin: An error occurred trying to delete your OpenAI API Key!", "red"); 220 | } 221 | } 222 | } 223 | } 224 | catch (Exception ex) 225 | { 226 | Logging.WriteToLog_Long($"OpenAI Plugin Error: Failure at KeyForm: {ex.Message}", "red"); 227 | } 228 | finally 229 | { 230 | OpenAIplugin.OpenAI_KeyFormOpen = false; 231 | } 232 | } 233 | 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/context/Moderation.cs: -------------------------------------------------------------------------------- 1 | using OpenAI_API; 2 | using System; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace OpenAI_VoiceAttack_Plugin 7 | { 8 | /// 9 | /// A class providing methods for evaluating text content with the OpenAI API Moderation 10 | /// via the library. 11 | /// 12 | /// 13 | ///
OpenAI API VoiceAttack Plugin
14 | ///
Copyright (C) 2023 Aaron Semler
15 | ///
github.com/SemlerPDX
16 | ///
veterans-gaming.com/semlerpdx-avcs
17 | ///

18 | /// This program is free software: you can redistribute it and/or modify
19 | /// it under the terms of the GNU General Public License as published by
20 | /// the Free Software Foundation, either version 3 of the License, or
21 | /// (at your option) any later version.
22 | ///
23 | /// This program is distributed in the hope that it will be useful,
24 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | /// GNU General Public License for more details.
27 | ///
28 | /// You should have received a copy of the GNU General Public License
29 | /// along with this program. If not, see gnu.org/licenses. 30 | ///
31 | public static class Moderation 32 | { 33 | /// 34 | /// A method to use OpenAI Moderation API to check user input text, and 35 | /// to provide the flagged categories which apply (if any). 36 | /// 37 | /// Sets the OpenAI_ContentFlagged boolean variable if content was flagged, or if not flagged. 38 | ///
Also sets the OpenAI_Response text variable to a string of flagged categories formatted for TTS, 39 | /// or to the original user input (unmodified) if not flagged.
40 | public static async Task Explain() 41 | { 42 | string userInput = OpenAIplugin.VA_Proxy.GetText("OpenAI_UserInput") ?? String.Empty; 43 | string categoriesString = String.Empty; 44 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_ContentFlagged", false); 45 | 46 | if (!String.IsNullOrEmpty(userInput)) 47 | { 48 | OpenAIAPI api = OpenAI_Key.LOAD_KEY 49 | ? new OpenAIAPI(new APIAuthentication(OpenAI_Key.API_KEY, OpenAI_Key.API_ORG)) 50 | : new OpenAIAPI(APIAuthentication.LoadFromPath( 51 | directory: OpenAI_Key.DEFAULT_KEY_FILEFOLDER, 52 | filename: OpenAI_Key.DEFAULT_KEY_FILENAME, 53 | searchUp: true 54 | )); 55 | 56 | var result = await api.Moderation.CallModerationAsync(userInput); 57 | 58 | 59 | if (result.Results[0].Flagged) 60 | { 61 | var flaggedCategories = result.Results[0].FlaggedCategories.ToList(); 62 | 63 | 64 | // Format any flagged categories for use in Text-to-Speech: 65 | if (flaggedCategories.Count == 1) 66 | { 67 | categoriesString = flaggedCategories[0]; 68 | } 69 | else if (flaggedCategories.Count == 2) 70 | { 71 | categoriesString = $"{flaggedCategories[0]} and {flaggedCategories[1]}"; 72 | } 73 | else 74 | { 75 | categoriesString = $"{string.Join(", ", flaggedCategories.Take(flaggedCategories.Count - 1))}, and {flaggedCategories.Last()}"; 76 | } 77 | 78 | } 79 | 80 | string response = result.Results[0].Flagged ? categoriesString : OpenAIplugin.VA_Proxy.GetText("OpenAI_UserInput"); 81 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", response); 82 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_ContentFlagged", result.Results[0].Flagged); 83 | } 84 | } 85 | 86 | /// 87 | /// A boolean method to use OpenAI Moderation API to check user input text. 88 | /// 89 | /// Sets the OpenAI_ContentFlagged boolean variable (and returns) if content was flagged, or if not flagged. 90 | ///
Also sets the OpenAI_Response text variable to a message stating input was inappropriate, or to the 91 | /// original user input (unmodified) if not flagged.
92 | public static async Task Check() 93 | { 94 | string userInput = OpenAIplugin.VA_Proxy.GetText("OpenAI_UserInput") ?? String.Empty; 95 | string flagMessage = OpenAIplugin.VA_Proxy.GetText("OpenAI_TTS_ContentFlagged") ?? "The input provided has been flagged as inappropriate."; 96 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_ContentFlagged", false); 97 | 98 | if (!String.IsNullOrEmpty(userInput)) 99 | { 100 | OpenAIAPI api = OpenAI_Key.LOAD_KEY 101 | ? new OpenAIAPI(new APIAuthentication(OpenAI_Key.API_KEY, OpenAI_Key.API_ORG)) 102 | : new OpenAIAPI(APIAuthentication.LoadFromPath( 103 | directory: OpenAI_Key.DEFAULT_KEY_FILEFOLDER, 104 | filename: OpenAI_Key.DEFAULT_KEY_FILENAME, 105 | searchUp: true 106 | )); 107 | 108 | var result = await api.Moderation.CallModerationAsync(userInput); 109 | 110 | string response = result.Results[0].Flagged 111 | ? flagMessage 112 | : OpenAIplugin.VA_Proxy.GetText("OpenAI_UserInput"); 113 | 114 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", response); 115 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_ContentFlagged", result.Results[0].Flagged); 116 | 117 | return result.Results[0].Flagged; 118 | } 119 | return false; 120 | } 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/context/Whisper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace OpenAI_VoiceAttack_Plugin 6 | { 7 | /// 8 | /// Provides access to methods for transcribing or translating audio into text using OpenAI by sending 9 | /// and receiving data through the OpenAI_NET App which provides access to its Whisper API. 10 | /// 11 | /// 12 | ///
OpenAI API VoiceAttack Plugin
13 | ///
Copyright (C) 2023 Aaron Semler
14 | ///
github.com/SemlerPDX
15 | ///
veterans-gaming.com/semlerpdx-avcs
16 | ///

17 | /// This program is free software: you can redistribute it and/or modify
18 | /// it under the terms of the GNU General Public License as published by
19 | /// the Free Software Foundation, either version 3 of the License, or
20 | /// (at your option) any later version.
21 | ///
22 | /// This program is distributed in the hope that it will be useful,
23 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | /// GNU General Public License for more details.
26 | ///
27 | /// You should have received a copy of the GNU General Public License
28 | /// along with this program. If not, see gnu.org/licenses. 29 | ///
30 | public static class Whisper 31 | { 32 | /// 33 | /// A default error message that can be set or checked against a response to communicate issues. 34 | /// 35 | public static readonly string DEFAULT_ERROR_MESSAGE = "Error processing audio file."; 36 | 37 | 38 | /// 39 | /// Get user input by post-processing Dictation Audio through the Whisper API using translation or transcription into English text. 40 | /// 41 | /// The type of Whisper operation to perform, either "transcription" or "translation". 42 | /// The returned text from Whisper, or an empty string if error has occurred. 43 | public static string GetUserInput(string operation) 44 | { 45 | string userInput = String.Empty; 46 | ProcessAudio(operation); 47 | 48 | if (OpenAIplugin.VA_Proxy.GetBoolean("OpenAI_Error") != true) 49 | { 50 | OpenAIplugin.VA_Proxy.SetText("OpenAI_UserInput", OpenAIplugin.VA_Proxy.GetText("OpenAI_Response") ?? String.Empty); 51 | userInput = OpenAIplugin.VA_Proxy.GetText("OpenAI_UserInput") ?? String.Empty; 52 | } 53 | return userInput; 54 | } 55 | 56 | /// 57 | /// Transcribe audio into text using OpenAI Whisper. 58 | /// 59 | /// 60 | /// Sets the OpenAI_Response text variable to a string that contains the transcribed if successful, 61 | /// or an error message if the operation fails. 62 | public static void ProcessAudio() { ProcessAudio("transcribe"); } 63 | /// 64 | /// Transcribe audio into text or Translate non-English audio into English text using OpenAI Whisper. 65 | /// 66 | /// The type of operation to perform, either "transcribe" or "translate". 67 | /// 68 | /// Sets the OpenAI_Response text variable to a string that contains the transcribed or translated text if successful, 69 | /// or an error message if the operation fails. 70 | public static void ProcessAudio(string operation) 71 | { 72 | try 73 | { 74 | string audioFilePath = OpenAIplugin.VA_Proxy.GetText("OpenAI_AudioPath") ?? (OpenAIplugin.VA_Proxy.GetText("OpenAI_AudioFile") ?? Configuration.DEFAULT_AUDIO_PATH); 75 | string response = DEFAULT_ERROR_MESSAGE; 76 | bool isError = false; 77 | 78 | 79 | // Ensure dictation audio file exists 80 | int timeoutMs = 5000; // 5 seconds 81 | int intervalMs = 100; // 100 milliseconds 82 | DateTime startTime = DateTime.Now; 83 | while (!System.IO.File.Exists(audioFilePath)) 84 | { 85 | // Check if the timeout has been reached 86 | if ((DateTime.Now - startTime).TotalMilliseconds >= timeoutMs) 87 | { 88 | throw new Exception("Dictation Audio File not found!"); 89 | } 90 | 91 | Thread.Sleep(intervalMs); 92 | } 93 | 94 | 95 | List args = new List 96 | { 97 | operation, 98 | OpenAI_Key.API_KEY, 99 | audioFilePath 100 | }; 101 | 102 | // Send the function request to the OpenAI_NET App 103 | if (!Piping.SendArgsToNamedPipe(args.ToArray())) 104 | { 105 | throw new Exception($"Failed to send {operation} request through pipe!"); 106 | } 107 | 108 | // Listen for the response from the OpenAI_NET App 109 | string[] responses = Piping.ListenForArgsOnNamedPipe(); 110 | if (responses != null && !String.IsNullOrEmpty(responses[0])) 111 | { 112 | response = responses[0]; 113 | if (response.StartsWith("OpenAI_NET")) 114 | { 115 | isError = true; 116 | } 117 | } 118 | else 119 | { 120 | isError = true; 121 | } 122 | 123 | // Set the error flag if necessary 124 | if (isError) 125 | { 126 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Error", true); 127 | } 128 | 129 | // Set the response to the VoiceAttack text variable and exit 130 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Response", response); 131 | } 132 | catch (Exception ex) 133 | { 134 | throw new Exception($"Whisper Error: {ex.Message}"); 135 | } 136 | } 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/packages.config: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | ".NETFramework,Version=v4.8": { 5 | "Microsoft.Bcl.AsyncInterfaces": { 6 | "type": "Direct", 7 | "requested": "[7.0.0, 7.0.0]", 8 | "resolved": "7.0.0", 9 | "contentHash": "3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==" 10 | }, 11 | "Microsoft.Extensions.DependencyInjection": { 12 | "type": "Direct", 13 | "requested": "[7.0.0, 7.0.0]", 14 | "resolved": "7.0.0", 15 | "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==" 16 | }, 17 | "Microsoft.Extensions.DependencyInjection.Abstractions": { 18 | "type": "Direct", 19 | "requested": "[7.0.0, 7.0.0]", 20 | "resolved": "7.0.0", 21 | "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" 22 | }, 23 | "Microsoft.Extensions.Http": { 24 | "type": "Direct", 25 | "requested": "[7.0.0, 7.0.0]", 26 | "resolved": "7.0.0", 27 | "contentHash": "9Pq9f/CvOSz0t9yQa6g1uWpxa2sm13daLFm8EZwy9MaQUjKXWdNUXQwIxwhmba5N83UIqURiPHSNqGK1vfWF2w==" 28 | }, 29 | "Microsoft.Extensions.Logging": { 30 | "type": "Direct", 31 | "requested": "[7.0.0, 7.0.0]", 32 | "resolved": "7.0.0", 33 | "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==" 34 | }, 35 | "Microsoft.Extensions.Logging.Abstractions": { 36 | "type": "Direct", 37 | "requested": "[7.0.0, 7.0.0]", 38 | "resolved": "7.0.0", 39 | "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" 40 | }, 41 | "Microsoft.Extensions.Options": { 42 | "type": "Direct", 43 | "requested": "[7.0.1, 7.0.1]", 44 | "resolved": "7.0.1", 45 | "contentHash": "pZRDYdN1FpepOIfHU62QoBQ6zdAoTvnjxFfqAzEd9Jhb2dfhA5i6jeTdgGgcgTWFRC7oT0+3XrbQu4LjvgX1Nw==" 46 | }, 47 | "Microsoft.Extensions.Primitives": { 48 | "type": "Direct", 49 | "requested": "[7.0.0, 7.0.0]", 50 | "resolved": "7.0.0", 51 | "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" 52 | }, 53 | "Microsoft.Win32.Registry": { 54 | "type": "Direct", 55 | "requested": "[5.0.0, 5.0.0]", 56 | "resolved": "5.0.0", 57 | "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" 58 | }, 59 | "NAudio": { 60 | "type": "Direct", 61 | "requested": "[2.1.0, 2.1.0]", 62 | "resolved": "2.1.0", 63 | "contentHash": "iamDtIq4Tcq6BDEMatX7Wj6KI7rscECS72ZejjnL9RK8BgfrVRH2RlhlQVNxXNp9Up1eFESEiCILNBo7//Rr0g==" 64 | }, 65 | "NAudio.Asio": { 66 | "type": "Direct", 67 | "requested": "[2.1.0, 2.1.0]", 68 | "resolved": "2.1.0", 69 | "contentHash": "YMXffgqPTsQIjmXYyzLrXSpQZzC7ZH49mmdXaPW3o/5S+AhFKZeOrwUA2fkvphQdGhryObbZnPAq+u8uABkH5A==" 70 | }, 71 | "NAudio.Core": { 72 | "type": "Direct", 73 | "requested": "[2.1.0, 2.1.0]", 74 | "resolved": "2.1.0", 75 | "contentHash": "0iowfmolibU4KxjaC/s7lox/BeDg76UVvXtCuCzRiNpylVndqsxXxp+bVWFrpv0KzQDIKLxZrnDmYENlAI0u9g==" 76 | }, 77 | "NAudio.Midi": { 78 | "type": "Direct", 79 | "requested": "[2.1.0, 2.1.0]", 80 | "resolved": "2.1.0", 81 | "contentHash": "m2ZunnyZ1p7OoAMh1scHRVle+GiFuIRrzkcxZJREUFHje8O2hcMGya3ZAjD+dV2FTcrbqNQUc4kJy9LOTuydUw==" 82 | }, 83 | "NAudio.Wasapi": { 84 | "type": "Direct", 85 | "requested": "[2.1.0, 2.1.0]", 86 | "resolved": "2.1.0", 87 | "contentHash": "Zp/8l79aYQEu7/HfzXWVpqyIwRUPgsvCj/Qx+pR6ykJxvwjqOoUfMiEG9US1wW4d7PEzT5ZBIL+cp90t6HNNiw==" 88 | }, 89 | "NAudio.WinForms": { 90 | "type": "Direct", 91 | "requested": "[2.1.0, 2.1.0]", 92 | "resolved": "2.1.0", 93 | "contentHash": "yoGCLidMVQmg66dNuEYusY1bttZJ5gxvufxRYFgxIw444dw4C70YsDgLeDugPd0rdwkSCQq5CtrVw6DSmxTxPA==" 94 | }, 95 | "NAudio.WinMM": { 96 | "type": "Direct", 97 | "requested": "[2.1.0, 2.1.0]", 98 | "resolved": "2.1.0", 99 | "contentHash": "lUU5C+XNzKhFUIEafA/nJpbtjksFtbUd033fs27KeHlOM0r6rQFF3l75dqxg+rWkGu81MfkBbYrNpH9p8qmtBA==" 100 | }, 101 | "Newtonsoft.Json": { 102 | "type": "Direct", 103 | "requested": "[13.0.3, 13.0.3]", 104 | "resolved": "13.0.3", 105 | "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" 106 | }, 107 | "OpenAI": { 108 | "type": "Direct", 109 | "requested": "[1.7.2, 1.7.2]", 110 | "resolved": "1.7.2", 111 | "contentHash": "veUa6xE+8ThjNLBCM75ZOOE0y72UNB/OQMsodOy+hTajZKV8So7s0dwx8x0kMqjgaiLMxC2grKGdQ+CVBl98sA==" 112 | }, 113 | "System.Buffers": { 114 | "type": "Direct", 115 | "requested": "[4.5.1, 4.5.1]", 116 | "resolved": "4.5.1", 117 | "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" 118 | }, 119 | "System.Diagnostics.DiagnosticSource": { 120 | "type": "Direct", 121 | "requested": "[7.0.2, 7.0.2]", 122 | "resolved": "7.0.2", 123 | "contentHash": "hYr3I9N9811e0Bjf2WNwAGGyTuAFbbTgX1RPLt/3Wbm68x3IGcX5Cl75CMmgT6WlNwLQ2tCCWfqYPpypjaf2xA==" 124 | }, 125 | "System.Memory": { 126 | "type": "Direct", 127 | "requested": "[4.5.5, 4.5.5]", 128 | "resolved": "4.5.5", 129 | "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" 130 | }, 131 | "System.Numerics.Vectors": { 132 | "type": "Direct", 133 | "requested": "[4.5.0, 4.5.0]", 134 | "resolved": "4.5.0", 135 | "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" 136 | }, 137 | "System.Runtime.CompilerServices.Unsafe": { 138 | "type": "Direct", 139 | "requested": "[6.0.0, 6.0.0]", 140 | "resolved": "6.0.0", 141 | "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" 142 | }, 143 | "System.Security.AccessControl": { 144 | "type": "Direct", 145 | "requested": "[6.0.0, 6.0.0]", 146 | "resolved": "6.0.0", 147 | "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" 148 | }, 149 | "System.Security.Principal.Windows": { 150 | "type": "Direct", 151 | "requested": "[5.0.0, 5.0.0]", 152 | "resolved": "5.0.0", 153 | "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" 154 | }, 155 | "System.Threading.Tasks.Extensions": { 156 | "type": "Direct", 157 | "requested": "[4.5.4, 4.5.4]", 158 | "resolved": "4.5.4", 159 | "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" 160 | }, 161 | "System.ValueTuple": { 162 | "type": "Direct", 163 | "requested": "[4.5.0, 4.5.0]", 164 | "resolved": "4.5.0", 165 | "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" 166 | } 167 | } 168 | } 169 | } -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/service/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Runtime.InteropServices; 5 | using System.Security; 6 | using System.Security.Permissions; 7 | using System.Windows; 8 | using System.Security.Principal; 9 | 10 | namespace OpenAI_VoiceAttack_Plugin 11 | { 12 | /// 13 | /// A class providing methods for plugin configuration initialization, creating the required 14 | /// default operational folders for this plugin at "%AppData%\Roaming\OpenAI_VoiceAttack_Plugin" 15 | /// 16 | /// 17 | ///
OpenAI API VoiceAttack Plugin
18 | ///
Copyright (C) 2023 Aaron Semler
19 | ///
github.com/SemlerPDX
20 | ///
veterans-gaming.com/semlerpdx-avcs
21 | ///

22 | /// This program is free software: you can redistribute it and/or modify
23 | /// it under the terms of the GNU General Public License as published by
24 | /// the Free Software Foundation, either version 3 of the License, or
25 | /// (at your option) any later version.
26 | ///
27 | /// This program is distributed in the hope that it will be useful,
28 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
29 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 | /// GNU General Public License for more details.
31 | ///
32 | /// You should have received a copy of the GNU General Public License
33 | /// along with this program. If not, see gnu.org/licenses. 34 | ///
35 | public static class Configuration 36 | { 37 | /// 38 | /// The folder path to the configuration folder containing OpenAI VoiceAttack Plugin files and folders. 39 | ///
40 | ///
Default Path: 41 | ///
"%AppData%\Roaming\OpenAI_VoiceAttack_Plugin" 42 | ///
43 | public static string DEFAULT_CONFIG_FOLDER = System.IO.Path.Combine( 44 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 45 | "OpenAI_VoiceAttack_Plugin"); 46 | 47 | /// 48 | /// The folder path to the captured audio files used for OpenAI Whisper 49 | /// 50 | public static readonly string DEFAULT_AUDIO_FOLDER = System.IO.Path.Combine(DEFAULT_CONFIG_FOLDER, "whisper"); 51 | 52 | /// 53 | /// The default file path to the captured audio file to use for OpenAI Whisper 54 | /// 55 | public static readonly string DEFAULT_AUDIO_PATH = System.IO.Path.Combine(DEFAULT_AUDIO_FOLDER, "dictation_audio.wav"); 56 | 57 | /// 58 | /// Method to check if application has permission to write file at path specified. 59 | /// 60 | /// File path to check write permissions for. 61 | /// True if has permissions, false if otherwise. 62 | /// 63 | /// Thrown when security error is detected. (returns false) 64 | /// 65 | private static bool OperationHasClearance(string filePath) 66 | { 67 | try 68 | { 69 | 70 | // Check if the application has elevated permissions 71 | bool hasElevatedPermissions = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); 72 | if (!hasElevatedPermissions) { return false; } 73 | 74 | FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Write, filePath); 75 | permission.Demand(); 76 | return true; 77 | } 78 | catch (SecurityException ex) 79 | { 80 | if (OpenAIplugin.DEBUG_ACTIVE == true) 81 | Logging.WriteToLog_Long($"The plugin has insufficient permissions for file operations: {ex.Message}", "red"); 82 | 83 | return false; 84 | } 85 | } 86 | 87 | /// 88 | /// A method to present a message box informing users to restart VoiceAttack following first time installation of shared assemblies. 89 | /// 90 | private static void ShowInstallationCompleteMessage() 91 | { 92 | System.Windows.MessageBox.Show("OpenAI Plugin for VoiceAttack has completed first time installation.\n" + 93 | "\n" + 94 | " You must now restart VoiceAttack one final time." + 95 | " You may also disable 'Run as Admin' now, if you prefer.", 96 | OpenAIplugin.VA_DisplayName(), 97 | MessageBoxButton.OK, 98 | MessageBoxImage.Information 99 | ); 100 | } 101 | 102 | /// 103 | /// The OpenAI Plugin requires the following files in the Shared\Assemblies folder of VoiceAttack, run only on first time installation.
104 | /// This method ensures these files are present before the Plugin can be used, informing users to restart one last time when complete.
105 | ///
Will set to if these assembly files were not already in shared assemblies folder: 106 | ///

107 | /// "Microsoft.Extensions.Options.dll"
108 | /// "Microsoft.Extensions.Http.dll"
109 | /// "Microsoft.Extensions.Primitives.dll"
110 | /// "System.Threading.Tasks.Extensions.dll"
111 | ///
112 | public static bool CheckSharedAssemblies() 113 | { 114 | string sourcePath; 115 | string destinationPath; 116 | string rootFolder = OpenAIplugin.VA_Proxy.AppsDir; 117 | string sharedFolder = OpenAIplugin.VA_Proxy.AssembliesDir; 118 | 119 | CreateNewDirectory(sharedFolder); 120 | 121 | // Array of required shared assemblies for OpenAI Plugin for VoiceAttack 122 | bool filesMoved = false; 123 | string[] assemblyNames = new[] 124 | { 125 | "Microsoft.Bcl.AsyncInterfaces.dll", 126 | "Microsoft.Extensions.Options.dll", 127 | "Microsoft.Extensions.Http.dll", 128 | "Microsoft.Extensions.Primitives.dll", 129 | "System.Threading.Tasks.Extensions.dll" 130 | }; 131 | 132 | // Check for and/or move files to Shared\Assemblies folder 133 | foreach (string assemblyName in assemblyNames) 134 | { 135 | sourcePath = Path.Combine(rootFolder, "OpenAI_Plugin", "shared", assemblyName); 136 | destinationPath = Path.Combine(sharedFolder, assemblyName); 137 | 138 | if (!File.Exists(destinationPath)) 139 | { 140 | if (OperationHasClearance(sharedFolder)) 141 | { 142 | filesMoved = true; 143 | File.Copy(sourcePath, destinationPath); 144 | } 145 | else 146 | { 147 | OpenAIplugin.VA_Proxy.WriteToLog("OpenAI Plugin Error: Must run VoiceAttack 'as Admin' for first time installation - see wiki!", "red"); 148 | return true; 149 | } 150 | } 151 | } 152 | 153 | // Inform of required restart only if files were added to Shared\Assemblies folder 154 | if (filesMoved) 155 | { 156 | OpenAIplugin.OpenAI_PluginFirstUse = true; 157 | ShowInstallationCompleteMessage(); 158 | } 159 | 160 | return filesMoved; 161 | } 162 | 163 | /// 164 | /// A method to delete the old dictation audio files after use, because the action which 165 | /// writes them cannot overwrite, and a new audio file is created each time, requiring cleanup. 166 | /// 167 | /// True when no audio folder exists to contain files, false if otherwise. 168 | public static bool DeleteOldDictationAudio() 169 | { 170 | try 171 | { 172 | if (Directory.Exists(DEFAULT_AUDIO_FOLDER)) 173 | { 174 | string[] files = Directory.GetFiles(DEFAULT_AUDIO_FOLDER); 175 | if (files.Length > 0) 176 | { 177 | foreach (string file in files) 178 | { 179 | if (file.EndsWith(".wav") && OperationHasClearance(file)) 180 | File.Delete(file); 181 | } 182 | } 183 | return false; 184 | } 185 | } 186 | catch 187 | { 188 | // ...let it slide, the plugin command also runs this in an inline function when the call ends 189 | } 190 | return true; 191 | } 192 | 193 | /// 194 | /// Create the folders required for OpenAI Plugin including the configuration and whisper folders 195 | /// 196 | /// The path of the folder to be created. 197 | /// Thrown when folder does not exist after attempt, or has insufficient permission to create, or other errors. 198 | private static void CreateNewDirectory(string directoryPath) 199 | { 200 | try 201 | { 202 | // Check if the folder path exists 203 | if (!System.IO.Directory.Exists(directoryPath)) 204 | { 205 | System.IO.Directory.CreateDirectory(directoryPath); 206 | if (!System.IO.Directory.Exists(directoryPath)) 207 | { 208 | throw new Exception($"Folder does not exist after attempting to create it at path: {directoryPath}"); 209 | } 210 | } 211 | } 212 | catch (Exception ex) 213 | { 214 | throw new Exception($"Failure to create new directory: {ex.Message}"); 215 | } 216 | } 217 | 218 | /// 219 | /// A method to create the required operational folders for this plugin to save captured audio files used in Whisper. 220 | /// 221 | /// A boolean indicating if this method should try creating config folders on first run. 222 | /// Thrown if unabled to create required config folders - plugin will not be initialized. 223 | public static void CreateConfigFolders(bool isFirstRun) 224 | { 225 | try 226 | { 227 | string whisperFolder = System.IO.Path.Combine(DEFAULT_CONFIG_FOLDER, "whisper"); 228 | 229 | // Try to create required config folders in AppData Roaming, throwing exceptions on failure 230 | if (isFirstRun) 231 | { 232 | CreateNewDirectory(DEFAULT_CONFIG_FOLDER); 233 | CreateNewDirectory(whisperFolder); 234 | } 235 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Plugin_ConfigFolderPath", DEFAULT_CONFIG_FOLDER); 236 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Plugin_WhisperFolderPath", whisperFolder); 237 | } 238 | catch (Exception ex) 239 | { 240 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Plugin_Initialized", false); 241 | throw new Exception($"Failure creating Config Folders in AppData Roaming! {ex.Message}"); 242 | } 243 | } 244 | 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/service/Dictation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using NAudio.Wave; 8 | using System.Threading; 9 | 10 | namespace OpenAI_VoiceAttack_Plugin 11 | { 12 | /// 13 | /// A class providing methods for getting and storing User Input for the class. 14 | /// 15 | /// 16 | ///
OpenAI API VoiceAttack Plugin
17 | ///
Copyright (C) 2023 Aaron Semler
18 | ///
github.com/SemlerPDX
19 | ///
veterans-gaming.com/semlerpdx-avcs
20 | ///

21 | /// This program is free software: you can redistribute it and/or modify
22 | /// it under the terms of the GNU General Public License as published by
23 | /// the Free Software Foundation, either version 3 of the License, or
24 | /// (at your option) any later version.
25 | ///
26 | /// This program is distributed in the hope that it will be useful,
27 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
28 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 | /// GNU General Public License for more details.
30 | ///
31 | /// You should have received a copy of the GNU General Public License
32 | /// along with this program. If not, see gnu.org/licenses. 33 | ///
34 | public static class Dictation 35 | { 36 | private static readonly string DEFAULT_DICTATIONSTART_COMMAND = "((OpenAI DictationStart))"; 37 | private static readonly string DEFAULT_DICTATIONSTOP_COMMAND = "((OpenAI DictationStop))"; 38 | 39 | /// 40 | /// A boolean to indicate that has ended with no user input. 41 | /// 42 | public static bool GetInputTimeout { get; set; } = false; 43 | 44 | /// 45 | /// The list of file names produced from a single dictation audio session. 46 | /// 47 | public static List DictationAudioFileNames { get; set; } = new List(); 48 | 49 | 50 | /// 51 | /// Generate a new audio file for the very last dictation audio capture (if dictation audio exists), 52 | /// using a new random GUID as the name, and storing the path to that file in the 'OpenAI_AudioFile' text variable. 53 | /// 54 | private static void SaveCapturedAudio() 55 | { 56 | // If empty, there is no dictation audio captured to save 57 | string newDictation = OpenAIplugin.VA_Proxy.ParseTokens("{DICTATION}") ?? String.Empty; 58 | if (String.IsNullOrEmpty(newDictation)) { return; } 59 | 60 | // Generate new random file name for the dictation audio capture 61 | string fileName = Guid.NewGuid().ToString(); 62 | string audioFilePath = System.IO.Path.Combine(Configuration.DEFAULT_AUDIO_FOLDER, fileName + ".wav"); 63 | 64 | // Store this variable for the CombineWavFiles method: 65 | DictationAudioFileNames.Add(audioFilePath); 66 | 67 | using (MemoryStream ms = OpenAIplugin.VA_Proxy.Utility.CapturedAudio(5)) 68 | { 69 | if (ms == null) 70 | return; 71 | 72 | using (FileStream fileStream = new FileStream(audioFilePath, FileMode.Create)) 73 | { 74 | ms.WriteTo(fileStream); 75 | } 76 | } 77 | 78 | // Store this variable for the Whisper Class as the current audio file target: 79 | OpenAIplugin.VA_Proxy.SetText("OpenAI_AudioFile", audioFilePath); // made null after plugin completes 80 | 81 | // Clear Dictation Buffer for the next inputs 82 | OpenAIplugin.VA_Proxy.Dictation.ClearBuffer(false, out string _); 83 | } 84 | 85 | 86 | /// 87 | /// Combines multiple input WAV files into a single output WAV file. 88 | /// 89 | /// A list of file paths to the input WAV files to be combined. 90 | /// Thrown when the input WAV files have different formats. 91 | public static void CombineWavFiles(List inputFilePaths) 92 | { 93 | if (inputFilePaths.Count > 1) 94 | { 95 | // Get the format of the first input file 96 | WaveFormat waveFormat = null; 97 | using (var waveFileReader = new WaveFileReader(inputFilePaths[0])) 98 | { 99 | waveFormat = waveFileReader.WaveFormat; 100 | } 101 | 102 | // Generate new random file name for the dictation audio capture 103 | string fileName = Guid.NewGuid().ToString(); 104 | string outputFilePath = System.IO.Path.Combine(Configuration.DEFAULT_AUDIO_FOLDER, fileName + ".wav"); 105 | 106 | // Create a new wave file writer with the same format as the input files 107 | using (var waveFileWriter = new WaveFileWriter(outputFilePath, waveFormat)) 108 | { 109 | foreach (var inputFilePath in inputFilePaths) 110 | { 111 | // Read each input file and append its data to the output file 112 | using (var waveFileReader = new WaveFileReader(inputFilePath)) 113 | { 114 | if (!waveFileReader.WaveFormat.Equals(waveFormat)) 115 | { 116 | throw new InvalidOperationException("Input files must have same format."); 117 | } 118 | 119 | byte[] buffer = new byte[waveFileReader.Length]; 120 | int bytesRead = waveFileReader.Read(buffer, 0, buffer.Length); 121 | waveFileWriter.Write(buffer, 0, bytesRead); 122 | } 123 | } 124 | } 125 | 126 | // Store this new variable for the Whisper Class as the combined audio file target: 127 | OpenAIplugin.VA_Proxy.SetText("OpenAI_AudioFile", outputFilePath); // made null ('Not set') after plugin completes 128 | } 129 | } 130 | 131 | 132 | 133 | /// 134 | /// The method for the 'GetInput' phase of a ChatGPT plugin context call. When the parameter is set to ,
135 | /// and also the 'OpenAI_TTS_PreListen' text variable contains a speech phrase or path to an audio file, it will execute a VoiceAttack
136 | /// profile command to speak a pre-listen phrase, or play a pre-listen sound, before gathering the user input from VoiceAttack Dictation
137 | /// in text and (optionally) audio file format, either through plugin methods, or through executing custom user command names stored in
138 | /// the 'OpenAI_Command_DictationStart' and/or 'OpenAI_Command_DictationStop' text variables. If the 'OpenAI_UserInput' text variable already has input, that will be returned. 139 | ///
140 | /// A boolean to indicate whether to generate a new audio file of the last Dictation, 141 | /// and set the path to that file in the 'OpenAI_AudioFile' text variable for Whisper transcription/translation later. 142 | /// A boolean to indicate if user feedback should be provided before listening begins. 143 | /// The raw text input gathered, either from an existing value set in 'OpenAI_UserInput', or from VoiceAttack Dictation Buffer. 144 | /// 145 | /// Thrown when user has set the name of a VoiceAttack command to the 'OpenAI_Command_DictationStop' text variable, 146 | /// but that command does not exist, nor does the default example command, named in 'DEFAULT_DICTATIONSTOP_COMMAND', 147 | /// and also the plugin method to generate and save Dictation audio to file have failed as well. 148 | /// 149 | public static string GetUserInput(bool generateAudioFile, bool sayPreListen) 150 | { 151 | // Clear Dictation Buffer and Start Dictation Mode 152 | OpenAIplugin.VA_Proxy.Dictation.ClearBuffer(false, out string _); 153 | 154 | // Check if users want to stop the VoiceAttack global 'Listening Mode' below when complete 155 | bool stopListeningMode = OpenAIplugin.VA_Proxy.GetBoolean("OpenAI_DisableListenMode") ?? false; 156 | 157 | // Check if default OR user custom 'Dictation Start' command exists in VoiceAttack profile: 158 | string lastSpokenCMD = OpenAIplugin.VA_Proxy.ParseTokens("{LASTSPOKENCMD}"); 159 | string listenCommand = OpenAIplugin.VA_Proxy.GetText("OpenAI_Command_DictationStart") ?? DEFAULT_DICTATIONSTART_COMMAND; 160 | if (!OpenAIplugin.VA_Proxy.Command.Exists(listenCommand) && OpenAIplugin.VA_Proxy.Command.Exists(DEFAULT_DICTATIONSTART_COMMAND)) 161 | listenCommand = DEFAULT_DICTATIONSTART_COMMAND; 162 | 163 | // Run custom profile based listen method for this phase of GetInput: 164 | if (OpenAIplugin.VA_Proxy.Command.Exists(listenCommand)) 165 | { 166 | OpenAIplugin.VA_Proxy.Command.Execute(listenCommand, true); // waits here for it to complete... 167 | // Post-Dictation break check to end a chat session and exit if the 'stop chatting' command is detected: 168 | bool chatSession = OpenAIplugin.CHAT_ACTIVE ?? false; 169 | if (!chatSession || OpenAIplugin._stopVariableToMonitor) 170 | return String.Empty; 171 | } 172 | 173 | // Set default OR user custom 'Dictation Stop' if command exists in VoiceAttack profile, or empty string if none: 174 | string dictationCommand = OpenAIplugin.VA_Proxy.GetText("OpenAI_Command_DictationStop") ?? DEFAULT_DICTATIONSTOP_COMMAND; 175 | if (!OpenAIplugin.VA_Proxy.Command.Exists(dictationCommand)) 176 | dictationCommand = OpenAIplugin.VA_Proxy.Command.Exists(DEFAULT_DICTATIONSTOP_COMMAND) ? DEFAULT_DICTATIONSTOP_COMMAND : String.Empty; 177 | 178 | // Check for manually provided input or otherwise produced {DICTATION} token value (if any): 179 | string userInput = OpenAIplugin.VA_Proxy.GetText("OpenAI_UserInput") ?? String.Empty; 180 | if (String.IsNullOrEmpty(userInput)) 181 | userInput = OpenAIplugin.VA_Proxy.ParseTokens("{DICTATION}") ?? String.Empty; 182 | 183 | // Fall-back input method for minimalist profile approach option: 184 | if (String.IsNullOrEmpty(userInput) && !OpenAIplugin.VA_Proxy.Command.Exists(listenCommand)) 185 | { 186 | // Optional speech before capturing input through Dictation text and audio capture 187 | if (sayPreListen && GetInputTimeout) 188 | ChatGPT.ProvideFeedback("OpenAI_TTS_PreListen", true); 189 | 190 | 191 | // End Dictation Mode 192 | if (OpenAIplugin.VA_Proxy.Dictation.IsOn()) 193 | { 194 | OpenAIplugin.VA_Proxy.Dictation.Stop(); 195 | 196 | // Clear Dictation Buffer before starting again 197 | OpenAIplugin.VA_Proxy.Dictation.ClearBuffer(false, out string _); 198 | } 199 | 200 | if (OpenAIplugin.VA_Proxy.Dictation.Start(out string _)) 201 | { 202 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Listening#", true); 203 | 204 | // Get custom user input timeout (convert to ms) - if none or zero, default used 205 | int timeoutMs = OpenAIplugin.VA_Proxy.GetInt("OpenAI_ListenTimeout_Seconds") ?? 0; 206 | timeoutMs = timeoutMs == 0 207 | ? 1800 // default of 1.8 seconds of no speech active to timeout 208 | : timeoutMs *= 1000; // convert custom user timeout into milliseconds 209 | 210 | // Set initial timeout (convert to ms) to allow speaker to form thoughts before speaking 211 | int initialTimeoutMs = OpenAIplugin.VA_Proxy.GetInt("OpenAI_ListenTimeout_InitialSeconds") ?? 0; 212 | initialTimeoutMs = initialTimeoutMs == 0 213 | ? 5000 // default of 1.8 seconds of no speech active to timeout 214 | : initialTimeoutMs *= 1000; // convert custom user timeout into milliseconds 215 | bool isInitialTimeout = true; 216 | 217 | int listenTimeoutMs = initialTimeoutMs; 218 | 219 | // Sets property to bypass this continuous back-and-forth conversational feature when no timeout possible 220 | GetInputTimeout = false; 221 | 222 | DateTime startTime = DateTime.Now; 223 | DateTime initialStartTime = DateTime.Now; 224 | 225 | int intervalMs = 100; // 100 milliseconds 226 | int writeInterval = 5000 / intervalMs; // calculate the 5 second interval for debugging output 227 | int index = 0; 228 | 229 | // Reset and declare Dictation containers 230 | DictationAudioFileNames.Clear(); 231 | string lastDictation = String.Empty; 232 | List appendedDictation = new List(); 233 | 234 | while (OpenAIplugin.LISTEN_ACTIVE == true && OpenAIplugin.VA_Proxy.Dictation.IsOn() && !OpenAIplugin._stopVariableToMonitor) 235 | { 236 | // Check if a non-session call was merely to an existing VoiceAttack command, and exit silently 237 | if (OpenAIplugin.CHAT_ACTIVE != true && lastSpokenCMD != OpenAIplugin.VA_Proxy.ParseTokens("{LASTSPOKENCMD}")) 238 | { 239 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Error", true); 240 | GetInputTimeout = true; 241 | break; 242 | } 243 | 244 | string newDictation = OpenAIplugin.VA_Proxy.ParseTokens("{DICTATION}") ?? String.Empty; 245 | if (!String.IsNullOrEmpty(newDictation) && newDictation != lastDictation) 246 | { 247 | lastDictation = newDictation; 248 | appendedDictation.Add(newDictation); 249 | 250 | // Save Dictation Audio for Whisper 251 | if (generateAudioFile) 252 | SaveCapturedAudio(); 253 | 254 | } 255 | 256 | // Check if the timeout has been reached or active chat manually ended 257 | if (OpenAIplugin.LISTEN_ACTIVE != true || !OpenAIplugin.VA_Proxy.State.SpeechActive() && ((DateTime.Now - startTime).TotalMilliseconds >= listenTimeoutMs)) 258 | { 259 | break; 260 | } 261 | 262 | // Modified from Pfeil's awesome 'Dictation until silence' post on VoiceAttack forums: 263 | if (OpenAIplugin.VA_Proxy.State.SpeechActive() || (isInitialTimeout && (DateTime.Now - initialStartTime).TotalMilliseconds >= initialTimeoutMs)) 264 | { 265 | isInitialTimeout = false; 266 | listenTimeoutMs = timeoutMs; 267 | startTime = DateTime.Now; 268 | } 269 | 270 | // Sleep for this interval and check for new user input 271 | Thread.Sleep(intervalMs); 272 | 273 | 274 | // Write 'still waiting' message every 5 seconds if debugging 275 | index++; 276 | if (OpenAIplugin.DEBUG_ACTIVE == true && index % writeInterval == 0) 277 | OpenAIplugin.VA_Proxy.WriteToLog("OpenAI ChatGPT is still waiting for user input to continue...", "yellow"); 278 | 279 | } 280 | 281 | if (!GetInputTimeout) 282 | { 283 | appendedDictation.Add(OpenAIplugin.VA_Proxy.ParseTokens("{DICTATION}") ?? String.Empty); 284 | userInput = string.Join(" ", appendedDictation).TrimEnd(); 285 | GetInputTimeout = String.IsNullOrWhiteSpace(userInput) || OpenAIplugin.LISTEN_ACTIVE != true; 286 | } 287 | 288 | // End Dictation Mode 289 | if (OpenAIplugin.VA_Proxy.Dictation.IsOn()) 290 | OpenAIplugin.VA_Proxy.Dictation.Stop(); 291 | 292 | // Must be reset to false when listening is ended 293 | OpenAIplugin.VA_Proxy.SetBoolean("OpenAI_Listening#", false); 294 | 295 | // Factor in a stop button press as equal to a timeout 296 | GetInputTimeout = GetInputTimeout || OpenAIplugin._stopVariableToMonitor; 297 | 298 | // Must ensure is empty if manually ended 299 | if (GetInputTimeout) 300 | userInput = String.Empty; 301 | 302 | // End VoiceAttack Global Listening on timeout if requested 303 | if (stopListeningMode && GetInputTimeout) 304 | OpenAIplugin.VA_Proxy.State.SetListeningEnabled(false); 305 | 306 | if (!String.IsNullOrEmpty(userInput) && !OpenAIplugin._stopVariableToMonitor) 307 | { 308 | // Check if default OR user custom 'Dictation Stop' command exists in VoiceAttack profile: 309 | if (OpenAIplugin.VA_Proxy.Command.Exists(dictationCommand)) 310 | { 311 | OpenAIplugin.VA_Proxy.Command.Execute(dictationCommand, true); // waits here for it to complete... 312 | } 313 | else 314 | { 315 | try 316 | { 317 | // Fall-back method for minimalist profile approach option: 318 | if (generateAudioFile) 319 | { 320 | // Save final dictation audio to file (if any) 321 | SaveCapturedAudio(); 322 | 323 | // Combine all audio files 324 | CombineWavFiles(DictationAudioFileNames); 325 | } 326 | } 327 | catch (Exception ex) 328 | { 329 | throw new Exception($"No valid '{DEFAULT_DICTATIONSTOP_COMMAND}' command available for chat session: {ex.Message}"); 330 | } 331 | } 332 | } 333 | 334 | OpenAIplugin._stopVariableToMonitor = false; 335 | } 336 | } 337 | return userInput; 338 | } 339 | 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/service/Logging.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using static System.Net.Mime.MediaTypeNames; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace OpenAI_VoiceAttack_Plugin 7 | { 8 | /// 9 | /// A class for methods providing a means to log data to the VoiceAttack Event Log, or elsewhere. 10 | /// 11 | /// 12 | ///
OpenAI API VoiceAttack Plugin
13 | ///
Copyright (C) 2023 Aaron Semler
14 | ///
github.com/SemlerPDX
15 | ///
veterans-gaming.com/semlerpdx-avcs
16 | ///

17 | /// This program is free software: you can redistribute it and/or modify
18 | /// it under the terms of the GNU General Public License as published by
19 | /// the Free Software Foundation, either version 3 of the License, or
20 | /// (at your option) any later version.
21 | ///
22 | /// This program is distributed in the hope that it will be useful,
23 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | /// GNU General Public License for more details.
26 | ///
27 | /// You should have received a copy of the GNU General Public License
28 | /// along with this program. If not, see gnu.org/licenses. 29 | ///
30 | public static class Logging 31 | { 32 | private static readonly long LOG_MAX_BYTES = 104857600L; // 100 MB max log size 33 | private static readonly string DEFAULT_LOG_NAME = "openai_errors"; 34 | private static string ERROR_LOG_PATH { get; set; } = Path.Combine( 35 | Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments), 36 | DEFAULT_LOG_NAME + ".log"); 37 | 38 | /// 39 | /// A method to write very long messages to the VoiceAttack Event Log, wrapping text 40 | /// which exceeds the minimum width of the window to a new event log entry on a new line. 41 | /// 42 | /// The text string containing the message to write to the Event Log without truncation. 43 | /// The color of the square to the left of each Event Log entry. 44 | public static void WriteToLog_Long(string longString, string colorString) 45 | { 46 | try 47 | { 48 | int maxWidth = 91; 49 | string[] lines = longString.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); 50 | 51 | foreach (string line in lines) 52 | { 53 | if (line.Length <= maxWidth) 54 | { 55 | OpenAIplugin.VA_Proxy.WriteToLog(line, colorString); 56 | continue; 57 | } 58 | 59 | int index = 0; 60 | while (index < line.Length) 61 | { 62 | try 63 | { 64 | int length = Math.Min(maxWidth, line.Length - index); 65 | 66 | if (length == maxWidth) 67 | { 68 | // Check if the line should wrap at a whitespace 69 | int lastSpaceIndex = line.LastIndexOf(' ', index + length, length); 70 | if (lastSpaceIndex != -1 && lastSpaceIndex != index + length) 71 | { 72 | length = lastSpaceIndex - index; 73 | } 74 | } 75 | 76 | OpenAIplugin.VA_Proxy.WriteToLog(line.Substring(index, length), colorString); 77 | index += length; 78 | 79 | // Ignore whitespace at the beginning of a new line 80 | while (index < line.Length && char.IsWhiteSpace(line[index])) 81 | { 82 | index++; 83 | } 84 | } 85 | catch (ArgumentOutOfRangeException) 86 | { 87 | // Catch ArgumentOutOfRangeException and continue the loop 88 | if (OpenAIplugin.DEBUG_ACTIVE == true) 89 | OpenAIplugin.VA_Proxy.WriteToLog("OpenAI Plugin Error: ArgumentOutOfRangeException caught at WriteToLog_Long() method", "red"); 90 | 91 | continue; 92 | } 93 | } 94 | } 95 | } 96 | catch 97 | { 98 | if (OpenAIplugin.DEBUG_ACTIVE == true) 99 | OpenAIplugin.VA_Proxy.WriteToLog("OpenAI Plugin Error: Failure ignored at WriteToLog_Long() method", "red"); 100 | } 101 | } 102 | 103 | /// 104 | /// A method to set the path for error logging during this session. 105 | /// 106 | public static void SetErrorLogPath() 107 | { 108 | try 109 | { 110 | // Use the default AppData Roaming folder with a sub-folder if an exception occurs 111 | if (Directory.Exists(Configuration.DEFAULT_CONFIG_FOLDER)) 112 | { 113 | ERROR_LOG_PATH = Path.Combine(Configuration.DEFAULT_CONFIG_FOLDER, DEFAULT_LOG_NAME + ".log"); 114 | } 115 | } 116 | catch (Exception ex) 117 | { 118 | WriteToLog_Long($"OpenAI Plugin Error: {ex.Message}", "red"); 119 | } 120 | finally 121 | { 122 | OpenAIplugin.VA_Proxy.SetText("OpenAI_Plugin_ErrorsLogPath", ERROR_LOG_PATH); 123 | } 124 | } 125 | 126 | private static bool CheckAndRotateErrorsLog(string path) 127 | { 128 | try 129 | { 130 | if (File.Exists(path)) 131 | { 132 | FileInfo file = new FileInfo(path); 133 | if (file.Length > LOG_MAX_BYTES) 134 | { 135 | string timestamp = DateTime.Now.ToString("MMddyyyyHHmmss"); 136 | string newFilePath = System.IO.Path.Combine(Configuration.DEFAULT_CONFIG_FOLDER, DEFAULT_LOG_NAME + "_" + timestamp + ".log"); 137 | File.Copy(path, newFilePath); 138 | File.Delete(path); 139 | } 140 | file = null; 141 | } 142 | return true; 143 | } 144 | catch (Exception ex) 145 | { 146 | WriteToLog_Long($"OpenAI Plugin Error: Error rotating oversized errors log: {ex.Message}", "red"); 147 | return false; 148 | } 149 | } 150 | 151 | /// 152 | /// A method for logging OpenAI Plugin error messages to the errors log file. 153 | ///
154 | ///
Default Path: 155 | ///
"%AppData%\Roaming\OpenAI_VoiceAttack_Plugin\openai_errors.log" 156 | ///
157 | /// The message to be appended to the log file. 158 | public static void WriteToLogFile(string logMessage) 159 | { 160 | try 161 | { 162 | if (CheckAndRotateErrorsLog(ERROR_LOG_PATH)) 163 | { 164 | using (StreamWriter writer = new StreamWriter(ERROR_LOG_PATH, true)) 165 | { 166 | writer.WriteLine("=========================================================================="); 167 | writer.WriteLine($"OpenAI Plugin Error at {DateTime.Now}:"); 168 | writer.WriteLine(logMessage); 169 | writer.WriteLine("=========================================================================="); 170 | writer.WriteLine(string.Empty); 171 | } 172 | } 173 | else 174 | { 175 | throw new Exception("OpenAIplugin.Logging.CheckAndRotateErrorsLog() returned false"); 176 | } 177 | } 178 | catch (Exception ex) 179 | { 180 | WriteToLog_Long($"OpenAI Plugin Error: unable to write to errors log file! Log Message: {logMessage} Failure Reason:{ex.Message}", "red"); 181 | } 182 | } 183 | 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/service/ModelsGPT.cs: -------------------------------------------------------------------------------- 1 | using OpenAI_API; 2 | using OpenAI_API.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace OpenAI_VoiceAttack_Plugin 7 | { 8 | /// 9 | /// A class providing methods for identifying the OpenAI_API and 10 | /// valid MaxTokens to use for a given OpenAI API request. 11 | /// 12 | /// 13 | ///
OpenAI API VoiceAttack Plugin
14 | ///
Copyright (C) 2023 Aaron Semler
15 | ///
github.com/SemlerPDX
16 | ///
veterans-gaming.com/semlerpdx-avcs
17 | ///

18 | /// This program is free software: you can redistribute it and/or modify
19 | /// it under the terms of the GNU General Public License as published by
20 | /// the Free Software Foundation, either version 3 of the License, or
21 | /// (at your option) any later version.
22 | ///
23 | /// This program is distributed in the hope that it will be useful,
24 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | /// GNU General Public License for more details.
27 | ///
28 | /// You should have received a copy of the GNU General Public License
29 | /// along with this program. If not, see gnu.org/licenses. 30 | ///
31 | public static class ModelsGPT 32 | { 33 | #region OpenAImodels 34 | private static readonly Model DEFAULT_CHAT_MODEL = Model.ChatGPTTurbo; 35 | private static readonly string DEFAULT_CHAT_MODEL_NAME = "ChatGPTTurbo"; 36 | 37 | private static readonly Model DEFAULT_COMPLETION_MODEL = Model.DavinciText; 38 | private static readonly string DEFAULT_COMPLETION_MODEL_NAME = "DavinciText"; 39 | 40 | /// 41 | /// A dictionary of GPT models with text string names as keys and their values as a corresponding OpenAI_API . 42 | /// 43 | private static Dictionary OpenAImodels { get; } = new Dictionary() 44 | { 45 | { "AdaText", Model.AdaText }, 46 | { "text-ada-001", Model.AdaText }, 47 | { "BabbageText", Model.BabbageText }, 48 | { "text-babbage-001", Model.BabbageText }, 49 | { "CurieText", Model.CurieText }, 50 | { "text-curie-001", Model.CurieText }, 51 | { "DavinciText", Model.DavinciText }, 52 | { "text-davinci-003", Model.DavinciText }, 53 | { "CushmanCode", Model.CushmanCode }, 54 | { "code-cushman-001", Model.CushmanCode }, 55 | { "DavinciCode", Model.DavinciCode }, 56 | { "code-davinci-002", Model.DavinciCode }, 57 | { "ChatGPTTurbo", Model.ChatGPTTurbo }, 58 | { "gpt-3.5-turbo", Model.ChatGPTTurbo }, 59 | { "ChatGPTTurbo0301", Model.ChatGPTTurbo0301 }, 60 | { "gpt-3.5-turbo-0301", Model.ChatGPTTurbo0301 }, 61 | { "GPT4", Model.GPT4 }, 62 | { "gpt-4", Model.GPT4 }, 63 | { "GPT4_32k_Context", Model.GPT4_32k_Context }, 64 | { "gpt-4-32k", Model.GPT4_32k_Context } 65 | }; 66 | 67 | /// 68 | /// A dictionary of GPT models as keys and their max tokens as integer values. 69 | /// 70 | private static Dictionary MaxTokensByModel { get; } = new Dictionary() 71 | { 72 | { Model.AdaText, 2048 }, 73 | { Model.BabbageText, 2048 }, 74 | { Model.CurieText, 2048 }, 75 | { Model.DavinciText, 4096 }, 76 | { Model.CushmanCode, 2048 }, 77 | { Model.DavinciCode, 8000 }, 78 | { Model.ChatGPTTurbo, 4096 }, 79 | { Model.ChatGPTTurbo0301, 4096 }, 80 | { Model.GPT4, 8192 }, 81 | { Model.GPT4_32k_Context, 32768 } 82 | }; 83 | 84 | private static List OpenAICompletionModels { get; } = new List() 85 | { 86 | "AdaText", 87 | "text-ada-001", 88 | "BabbageText", 89 | "text-babbage-001", 90 | "CurieText", 91 | "text-curie-001", 92 | "DavinciText", 93 | "text-davinci-003", 94 | "CushmanCode", 95 | "code-cushman-001", 96 | "DavinciCode", 97 | "code-davinci-002" 98 | }; 99 | 100 | private static List OpenAIChatGPTmodels { get; } = new List() 101 | { 102 | "ChatGPTTurbo", 103 | "gpt-3.5-turbo", 104 | "ChatGPTTurbo0301", 105 | "gpt-3.5-turbo-0301", 106 | "GPT4", 107 | "gpt-4", 108 | "GPT4_32k_Context", 109 | "gpt-4-32k" 110 | }; 111 | #endregion 112 | 113 | 114 | /// 115 | /// A method to get the specified in the 'OpenAI_Model' VoiceAttack text variable. 116 | ///
See also and
117 | ///
118 | /// The corresponding to the provided model name, 119 | /// or if not set (or no match found). 120 | /// 121 | /// On any exceptions, the default will be set, and notify users of the error. 122 | /// 123 | public static Model GetOpenAI_Model() { return GetOpenAI_Model(false); } 124 | /// 125 | /// A method to get the specified in the 'OpenAI_Model' VoiceAttack text variable. 126 | ///
See also and
127 | ///
128 | /// 129 | /// A boolean to return only a model which can be used in Completion ( if not set, or no match found). 130 | /// The corresponding to the provided model name. 131 | ///
or will be returned (as appropriate) if not set, or no match found.
132 | /// 133 | /// On any exceptions, the default will be set, and notify users of the error. 134 | /// 135 | public static Model GetOpenAI_Model(bool completionModel) 136 | { 137 | try 138 | { 139 | string modelName = OpenAIplugin.VA_Proxy.GetText("OpenAI_Model") ?? (completionModel 140 | ? DEFAULT_CHAT_MODEL_NAME 141 | : DEFAULT_COMPLETION_MODEL_NAME); 142 | 143 | if (completionModel) 144 | { 145 | if (!OpenAICompletionModels.Contains(modelName)) 146 | { 147 | modelName = DEFAULT_COMPLETION_MODEL_NAME; 148 | } 149 | } 150 | else 151 | { 152 | if (!OpenAIChatGPTmodels.Contains(modelName)) 153 | { 154 | modelName = DEFAULT_CHAT_MODEL_NAME; 155 | } 156 | } 157 | 158 | if (OpenAImodels.TryGetValue(modelName, out Model model)) 159 | { 160 | // Use the "model" object corresponding to the name users provided 161 | return model; 162 | } 163 | } 164 | catch (ArgumentNullException ex) 165 | { 166 | Logging.WriteToLog_Long($"OpenAI Plugin Error in TryGetValue: {ex.Message}", "red"); 167 | OpenAIplugin.VA_Proxy.WriteToLog("The default 'ChatGPTTurbo' model will be used instead.", "blank"); 168 | } 169 | catch (Exception ex) 170 | { 171 | Logging.WriteToLog_Long($"OpenAI Plugin Error: {ex.Message}", "red"); 172 | OpenAIplugin.VA_Proxy.WriteToLog("The default 'ChatGPTTurbo' model will be used instead.", "blank"); 173 | } 174 | 175 | // Handle the case where the user specified an unknown or invalid model name 176 | return completionModel ? DEFAULT_COMPLETION_MODEL : DEFAULT_CHAT_MODEL; 177 | } 178 | 179 | 180 | /// 181 | /// A method to check 'OpenAI_MaxTokens' integer variable and return a max tokens integer no greater than the max tokens of the 182 | /// supplied . 183 | ///
See also
184 | ///
185 | /// The GPT model being used. 186 | /// An integer of max tokens to use which is no greater than the allows, or 512 if unset/invalid. 187 | public static int GetValidMaxTokens(Model gptModel) 188 | { 189 | try 190 | { 191 | int maxTokens = OpenAIplugin.VA_Proxy.GetInt("OpenAI_MaxTokens") ?? 0; 192 | if (MaxTokensByModel.TryGetValue(gptModel, out int maxAllowed) && maxTokens > maxAllowed) 193 | { 194 | return maxAllowed; 195 | } 196 | return maxTokens > 0 ? maxTokens : 512; 197 | } 198 | catch (Exception ex) 199 | { 200 | Logging.WriteToLog_Long($"OpenAI Plugin Error: GetValidMaxTokens exception: {ex.Message}", "red"); 201 | return 512; 202 | } 203 | } 204 | 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/service/OpenAI_Key.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace OpenAI_VoiceAttack_Plugin 5 | { 6 | /// 7 | /// A class providing the method for obtaining the user's OpenAI API key used for API calls throughout 8 | /// this VoiceAttack plugin (and the OpenAI_NET companion app, for Whisper and Dall-E) 9 | /// 10 | /// 11 | ///
OpenAI API VoiceAttack Plugin
12 | ///
Copyright (C) 2023 Aaron Semler
13 | ///
github.com/SemlerPDX
14 | ///
veterans-gaming.com/semlerpdx-avcs
15 | ///

16 | /// This program is free software: you can redistribute it and/or modify
17 | /// it under the terms of the GNU General Public License as published by
18 | /// the Free Software Foundation, either version 3 of the License, or
19 | /// (at your option) any later version.
20 | ///
21 | /// This program is distributed in the hope that it will be useful,
22 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 | /// GNU General Public License for more details.
25 | ///
26 | /// You should have received a copy of the GNU General Public License
27 | /// along with this program. If not, see gnu.org/licenses. 28 | ///
29 | public static class OpenAI_Key 30 | { 31 | #region openai key properties 32 | /// 33 | /// The path to the folder containing the OpenAI API Key file. 34 | /// 35 | public static string DEFAULT_KEY_FILEFOLDER { get; set; } = Configuration.DEFAULT_CONFIG_FOLDER; 36 | 37 | /// 38 | /// The name of the keyfile containing the OpenAI API Key. 39 | /// 40 | public static string DEFAULT_KEY_FILENAME { get; set; } = "key.openai"; 41 | 42 | /// 43 | /// A boolean to indicate the method for applying the OpenAI Key to API calls.
44 | /// True will load from file (see documentation), False will throw exceptions when no key is set. 45 | ///
46 | public static bool LOAD_KEY { get; set; } = false; 47 | 48 | /// 49 | /// The end user's private API key for their OpenAI API account.
50 | ///
See OpenAI documentation at openai.com/docs/api-reference 51 | ///
52 | public static string API_KEY { get; set; } 53 | 54 | /// 55 | /// A stand-in example of the end user's private API key to their OpenAI API account.
56 | ///
See OpenAI documentation at openai.com/docs/api-reference 57 | ///
58 | public static readonly string EXAMPLE_API_KEY = "sk-12345abcdefghijklmnopqrstuvwxyz"; 59 | 60 | /// 61 | /// The end user's private API key Organization for their OpenAI API account. (OPTIONAL)
62 | ///
Can be - See OpenAI documentation at openai.com/docs/api-reference 63 | ///
64 | public static string API_ORG { get; set; } 65 | #endregion 66 | 67 | private static string KeyFilePath() 68 | { 69 | try 70 | { 71 | // Check for custom key filename 72 | string apiKeyFileName = OpenAIplugin.VA_Proxy.GetText("OpenAI_API_Key_FileName") ?? DEFAULT_KEY_FILENAME; 73 | DEFAULT_KEY_FILENAME = apiKeyFileName != DEFAULT_KEY_FILENAME ? apiKeyFileName : DEFAULT_KEY_FILENAME; 74 | 75 | // Check for custom key file folder 76 | string apiKeyFolder = OpenAIplugin.VA_Proxy.GetText("OpenAI_API_Key_FileFolder") ?? Configuration.DEFAULT_CONFIG_FOLDER; 77 | DEFAULT_KEY_FILEFOLDER = System.IO.Directory.Exists(apiKeyFolder) && System.IO.File.Exists(System.IO.Path.Combine(apiKeyFolder, apiKeyFileName)) 78 | ? apiKeyFolder 79 | : Configuration.DEFAULT_CONFIG_FOLDER; 80 | 81 | return System.IO.Path.Combine(DEFAULT_KEY_FILEFOLDER, DEFAULT_KEY_FILENAME); 82 | } 83 | catch 84 | { 85 | return String.Empty; 86 | } 87 | } 88 | 89 | /// 90 | /// A method for loading the users OpenAI API key from file. 91 | /// 92 | /// The users private OpenAI API key. 93 | public static string LoadFromFile() 94 | { 95 | try 96 | { 97 | string keyFile = KeyFilePath(); 98 | string apiKey = String.Empty; 99 | if (File.Exists(keyFile)) 100 | { 101 | // Read the contents of the key file into the return string 102 | using (StreamReader reader = new StreamReader(keyFile)) 103 | { 104 | string key = reader.ReadToEnd().Trim(); 105 | apiKey = key; 106 | } 107 | 108 | if (apiKey.Contains(Environment.NewLine)) 109 | { 110 | string[] apiKeyLines = apiKey.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); 111 | foreach (string line in apiKeyLines) 112 | { 113 | if (line.Contains("_KEY=")) 114 | { 115 | string[] apiKeyVar = line.Split('='); 116 | apiKey = apiKeyVar[1]; 117 | } 118 | } 119 | } 120 | else 121 | { 122 | // Parse out the key (default format OPENAI_API_KEY=key) 123 | if (apiKey.Contains("_KEY=")) 124 | { 125 | string[] apiKeyVar = apiKey.Split('='); 126 | apiKey = apiKeyVar[1]; 127 | } 128 | } 129 | 130 | } 131 | return apiKey; 132 | } 133 | catch (Exception ex) 134 | { 135 | Logging.WriteToLog_Long($"OpenAI Plugin Error: Failure to read the OpenAI API Key from file! {ex.Message}", "red"); 136 | return String.Empty; 137 | } 138 | } 139 | 140 | /// 141 | /// A method for saving the users OpenAI API Key to their key file. 142 | ///
143 | ///
Default Path: 144 | ///
"%AppData%\Roaming\OpenAI_VoiceAttack_Plugin\key.openai" 145 | ///
146 | public static bool SaveToFile(string key) 147 | { 148 | try 149 | { 150 | if (!key.StartsWith("OPENAI_API_KEY=")) 151 | key = $"OPENAI_API_KEY={key}"; 152 | 153 | string keyFile = KeyFilePath(); 154 | using (StreamWriter writer = new StreamWriter(keyFile, false)) 155 | { 156 | writer.Write(key); 157 | } 158 | return true; 159 | } 160 | catch (Exception ex) 161 | { 162 | Logging.WriteToLog_Long($"OpenAI Plugin Error: Failure to write to OpenAI API Key to file! {ex.Message}", "red"); 163 | return false; 164 | } 165 | } 166 | 167 | /// 168 | /// A method to delete the old dictation audio files after use, because the action which 169 | /// writes them cannot overwrite, and a new audio file is created each time, requiring cleanup. 170 | /// 171 | public static bool DeleteFromFile() 172 | { 173 | try 174 | { 175 | string keyFile = System.IO.Path.Combine(OpenAI_Key.DEFAULT_KEY_FILEFOLDER, OpenAI_Key.DEFAULT_KEY_FILENAME); 176 | if (Directory.Exists(OpenAI_Key.DEFAULT_KEY_FILEFOLDER)) 177 | { 178 | if (File.Exists(keyFile)) 179 | { 180 | File.Delete(keyFile); 181 | 182 | } 183 | } 184 | return true; 185 | } 186 | catch (Exception ex) 187 | { 188 | Logging.WriteToLog_Long($"OpenAI Plugin Error: Failure to delete the OpenAI API Key from file! {ex.Message}", "red"); 189 | return false; 190 | } 191 | } 192 | 193 | 194 | /// 195 | /// Retrieve the users OpenAI API Key from the 'OpenAI_API_Key' variable, or from file at default or custom location.
196 | /// Path to file can be variable when user provides 'OpenAI_API_Key_FileName' and/or 'OpenAI_API_Key_FileFolder' text variables. 197 | ///
198 | /// The end user's private API key for their OpenAI API account. 199 | public static string GetOpenAI_Key() 200 | { 201 | try 202 | { 203 | // Check first for already provided (valid) key 204 | string apiKey = OpenAIplugin.VA_Proxy.GetText("OpenAI_API_Key") ?? (OpenAI_Key.API_KEY ?? String.Empty); 205 | if (String.IsNullOrEmpty(apiKey) || apiKey == EXAMPLE_API_KEY) 206 | { 207 | 208 | string filePath = KeyFilePath(); 209 | if (String.IsNullOrEmpty(filePath)) { throw new Exception("No API Key found!"); } 210 | 211 | LOAD_KEY = System.IO.File.Exists(filePath) || LOAD_KEY; 212 | if (LOAD_KEY) 213 | { 214 | apiKey = LoadFromFile() ?? String.Empty; 215 | } 216 | else 217 | { 218 | KeyForm keyForm = new KeyForm(); 219 | keyForm.ShowKeyInputForm(); 220 | keyForm = null; 221 | apiKey = OpenAI_Key.API_KEY; 222 | } 223 | } 224 | 225 | if (String.IsNullOrEmpty(apiKey)) { throw new Exception("No API Key found!"); } 226 | if (apiKey == EXAMPLE_API_KEY) { throw new Exception("Invalid API Key found! See documentation!"); } 227 | 228 | return apiKey; 229 | } 230 | catch (Exception ex) 231 | { 232 | throw new Exception($"{ex.Message}"); 233 | } 234 | } 235 | 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/service/OpenAI_NET.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | namespace OpenAI_VoiceAttack_Plugin 6 | { 7 | /// 8 | /// A class providing methods for launching the OpenAI_NET companion app which 9 | /// provides Whisper and Dall-E interprocess functions to this VoiceAttack Plugin. 10 | /// 11 | /// 12 | ///
OpenAI API VoiceAttack Plugin
13 | ///
Copyright (C) 2023 Aaron Semler
14 | ///
github.com/SemlerPDX
15 | ///
veterans-gaming.com/semlerpdx-avcs
16 | ///

17 | /// This program is free software: you can redistribute it and/or modify
18 | /// it under the terms of the GNU General Public License as published by
19 | /// the Free Software Foundation, either version 3 of the License, or
20 | /// (at your option) any later version.
21 | ///
22 | /// This program is distributed in the hope that it will be useful,
23 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | /// GNU General Public License for more details.
26 | ///
27 | /// You should have received a copy of the GNU General Public License
28 | /// along with this program. If not, see gnu.org/licenses. 29 | ///
30 | public static class OpenAI_NET 31 | { 32 | 33 | /// 34 | /// Check if the OpenAI_NET companion app is running and therefore listening for Whisper and DALL-E requests. 35 | /// 36 | /// True if the OpenAI_NET.exe is running, false if otherwise. 37 | /// Thrown when an error occurs in GetProcessesByName(). 38 | public static bool IsRunningOpenAI_NET() 39 | { 40 | string errorMessage = "OpenAI Plugin Error in IsRunningOpenAI_NET"; 41 | try 42 | { 43 | Process[] processes = Process.GetProcessesByName("OpenAI_NET"); 44 | return (processes.Length > 0); 45 | } 46 | catch (InvalidOperationException ex) 47 | { 48 | Logging.WriteToLog_Long($"{errorMessage}: {ex.Message}", "red"); 49 | } 50 | catch (Exception ex) 51 | { 52 | Logging.WriteToLog_Long($"{errorMessage}: {ex.Message}", "red"); 53 | } 54 | return false; 55 | } 56 | 57 | /// 58 | /// Launch the OpenAI_NET companion app, which listens for Whisper and DALL-E requests. 59 | /// 60 | /// True if OpenAI_NET.exe was successfully launched, false if otherwise. 61 | /// Thrown when an error occurs in Start(). 62 | /// Thrown in Start() when the OpenAI_NET.exe file doesn't exist. 63 | public static bool LaunchOpenAI_NET() 64 | { 65 | string errorMessage = "OpenAI Plugin Error in LaunchOpenAI_NET"; 66 | try 67 | { 68 | string filePath = System.IO.Path.Combine(OpenAIplugin.VA_Proxy.AppsDir, @"OpenAI_Plugin\OpenAI_NET.exe"); 69 | Process.Start(filePath); 70 | return true; 71 | } 72 | catch (ObjectDisposedException ex) 73 | { 74 | Logging.WriteToLog_Long($"{errorMessage}: {ex.Message}", "red"); 75 | } 76 | catch (FileNotFoundException) 77 | { 78 | Logging.WriteToLog_Long($"{errorMessage}: Cannot locate OpenAI_NET.exe in VA Apps OpenAI Plugin folder!", "red"); 79 | } 80 | catch (Exception ex) 81 | { 82 | Logging.WriteToLog_Long($"{errorMessage}: {ex.Message}", "red"); 83 | } 84 | return false; 85 | } 86 | 87 | /// 88 | /// Terminate the OpenAI_NET.exe process(es) or fail silently. 89 | /// 90 | /// Thrown when an error occurs in GetProcessesByName() or Kill(). 91 | /// Thrown when an error occurs in Kill(). 92 | public static void KillOpenAI_NET() 93 | { 94 | try 95 | { 96 | Process[] processes = Process.GetProcessesByName("OpenAI_NET"); 97 | foreach (Process process in processes) 98 | { 99 | process.Kill(); 100 | } 101 | } 102 | catch 103 | { 104 | //...let it slide and get out of Dodge 105 | } 106 | } 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/service/Piping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Pipes; 5 | 6 | namespace OpenAI_VoiceAttack_Plugin 7 | { 8 | /// 9 | /// A class providing methods for interprocess communications 10 | /// between this VoiceAttack Plugin and the OpenAI_NET application. 11 | /// 12 | /// 13 | ///
OpenAI API VoiceAttack Plugin
14 | ///
Copyright (C) 2023 Aaron Semler
15 | ///
github.com/SemlerPDX
16 | ///
veterans-gaming.com/semlerpdx-avcs
17 | ///

18 | /// This program is free software: you can redistribute it and/or modify
19 | /// it under the terms of the GNU General Public License as published by
20 | /// the Free Software Foundation, either version 3 of the License, or
21 | /// (at your option) any later version.
22 | ///
23 | /// This program is distributed in the hope that it will be useful,
24 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
25 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 | /// GNU General Public License for more details.
27 | ///
28 | /// You should have received a copy of the GNU General Public License
29 | /// along with this program. If not, see gnu.org/licenses. 30 | ///
31 | public static class Piping 32 | { 33 | private static readonly string PipeNameTHIS = "OpenAI Plugin"; 34 | private static readonly string PipeNameIN = "OpenAI_Plugin_Pipe"; 35 | private static readonly string PipeNameOUT = "OpenAI_NET_Pipe"; 36 | 37 | /// 38 | /// A method to pipe a function request to the OpenAI_NET application, 39 | /// which offers access to OpenAI Whisper and Dall-E Image editing/variation. 40 | /// 41 | /// A string array starting with the desired function, 42 | /// followed by the OpenAI API Key (and optional organization id), then any required parameters (see documentation). 43 | /// True upon success, false if otherwise. 44 | /// Thrown when an error occurs in WriteLine(). 45 | /// Thrown when an error occurs in WriteLine(). 46 | /// Thrown when an error occurs in Connect(). 47 | public static bool SendArgsToNamedPipe(string[] args) 48 | { 49 | if (args == null || args.Length == 0 || args.Length < 3) 50 | { 51 | throw new Exception($"The {PipeNameTHIS} arguments array must contain at least three elements."); 52 | } 53 | 54 | try 55 | { 56 | // Attach the Organization ID to the args if set 57 | if (!String.IsNullOrEmpty(OpenAI_Key.API_ORG)) 58 | { 59 | args[1] = $"{args[1]}:{OpenAI_Key.API_ORG}"; 60 | } 61 | 62 | // Send the args over the named pipe to the OpenAI_NET companion app 63 | using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", PipeNameOUT, PipeDirection.Out)) 64 | { 65 | if (!pipeClient.IsConnected) 66 | pipeClient.Connect(); 67 | 68 | using (StreamWriter writer = new StreamWriter(pipeClient)) 69 | { 70 | foreach (string arg in args) 71 | { 72 | if (!String.IsNullOrEmpty(arg)) 73 | writer.WriteLine(arg); 74 | } 75 | } 76 | } 77 | } 78 | catch (Exception ex) 79 | { 80 | if (OpenAIplugin.DEBUG_ACTIVE == true) 81 | Logging.WriteToLog_Long($"{PipeNameTHIS} Error: SendArgsToNamedPipe Exception occurred: {ex.Message}", "red"); 82 | } 83 | return true; 84 | } 85 | 86 | /// 87 | /// A method to listen for piped function responses from the OpenAI_NET application, 88 | /// which offers access to OpenAI Whisper and Dall-E Image editing/variation. 89 | /// 90 | /// A string array starting with the requested function response, 91 | /// followed by additional response items (if any) (see documentation). 92 | /// Thrown when an error occurs in ReadLine(). 93 | /// Thrown when an error occurs in WaitForConnection() or ReadLine(). 94 | /// Thrown when an error occurs in WaitForConnection() or Disconnect(). 95 | /// Thrown when an error occurs in WaitForConnection() or Disconnect(). 96 | public static string[] ListenForArgsOnNamedPipe() 97 | { 98 | List args = new List(); 99 | try 100 | { 101 | // Listen for return args over the named pipe from the OpenAI_NET companion app 102 | using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(PipeNameIN, PipeDirection.In, NamedPipeServerStream.MaxAllowedServerInstances)) 103 | { 104 | 105 | pipeServer.WaitForConnection(); 106 | 107 | using (StreamReader reader = new StreamReader(pipeServer)) 108 | { 109 | string line; 110 | while ((line = reader.ReadLine() ?? String.Empty) != null && !String.IsNullOrEmpty(line)) 111 | { 112 | args.Add(line); 113 | } 114 | } 115 | 116 | pipeServer.Dispose(); 117 | } 118 | } 119 | catch (Exception ex) 120 | { 121 | if (OpenAIplugin.DEBUG_ACTIVE == true) 122 | Logging.WriteToLog_Long($"{PipeNameTHIS} Error: ListenForArgsOnNamedPipe Exception occurred: {ex.Message}", "red"); 123 | } 124 | 125 | return args.ToArray(); 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /OpenAI_VoiceAttack_Plugin/service/Updates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net; 4 | using System.Reflection; 5 | using System.Speech.Synthesis; 6 | using System.Windows; 7 | using static System.Net.Mime.MediaTypeNames; 8 | 9 | namespace OpenAI_VoiceAttack_Plugin 10 | { 11 | /// 12 | /// A class providing methods to check for plugin updates. If found, speaks a simple message using TTS and also prints redirect link to the GitHub repo. 13 | /// 14 | /// 15 | ///
OpenAI API VoiceAttack Plugin
16 | ///
Copyright (C) 2023 Aaron Semler
17 | ///
github.com/SemlerPDX
18 | ///
veterans-gaming.com/semlerpdx-avcs
19 | ///

20 | /// This program is free software: you can redistribute it and/or modify
21 | /// it under the terms of the GNU General Public License as published by
22 | /// the Free Software Foundation, either version 3 of the License, or
23 | /// (at your option) any later version.
24 | ///
25 | /// This program is distributed in the hope that it will be useful,
26 | /// but WITHOUT ANY WARRANTY; without even the implied warranty of
27 | /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 | /// GNU General Public License for more details.
29 | ///
30 | /// You should have received a copy of the GNU General Public License
31 | /// along with this program. If not, see gnu.org/licenses. 32 | ///
33 | public static class Updates 34 | { 35 | private static readonly string PluginVersionPage = "https://veterans-gaming.com/semlerpdx/vglabs/downloads/voiceattack-plugin-openai/version.html"; 36 | private static readonly string PluginDownloadPage = "veterans-gaming.com/semlerpdx/openai"; 37 | 38 | 39 | /// 40 | /// A string indicating the latest version of this plugin, changed if an update is found. 41 | /// 42 | public static string LatestVersion { get; set; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); 43 | 44 | /// 45 | /// Check the raw version number page for this plugin on the secure VG website. 46 | /// 47 | /// A string containing the latest version, or an empty string upon failure. 48 | private static string GetLatestVersion() 49 | { 50 | try 51 | { 52 | using (var client = new WebClient()) 53 | { 54 | string version = client.DownloadString(PluginVersionPage); 55 | return version.Trim(); 56 | } 57 | } 58 | catch (Exception ex) 59 | { 60 | Logging.WriteToLog_Long($"OpenAI Plugin Error: {ex.Message}", "red"); 61 | return String.Empty; 62 | } 63 | } 64 | 65 | /// 66 | /// A very simple update check that runs one time when the plugin is loadeded by VoiceAttack in the method.. 67 | /// 68 | /// True when update has been found, false if otherwise. 69 | public static bool UpdateCheck() 70 | { 71 | try 72 | { 73 | // Get current version 74 | string currentVersion = LatestVersion; 75 | 76 | // Get latest version 77 | string latestVersion = GetLatestVersion(); 78 | if (String.IsNullOrEmpty(latestVersion)) 79 | { 80 | return false; 81 | } 82 | 83 | LatestVersion = latestVersion; 84 | 85 | // Check if update notice should be shown 86 | if (new Version(currentVersion) < new Version(latestVersion)) 87 | { 88 | return true; 89 | } 90 | } 91 | catch (Exception ex) 92 | { 93 | Logging.WriteToLog_Long($"OpenAI Plugin Error: {ex.Message}", "red"); 94 | } 95 | return false; 96 | } 97 | 98 | /// 99 | /// A very simple update check message that speaks if the current version is below the latest published version.
100 | /// This can only happen once per session, during init. 101 | ///
102 | public static void UpdateMessage() 103 | { 104 | try 105 | { 106 | 107 | // Get current version 108 | string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); 109 | 110 | OpenAIplugin.VA_Proxy.WriteToLog("", "blank"); 111 | OpenAIplugin.VA_Proxy.WriteToLog($"Please update the OpenAI Plugin for VoiceAttack to v{LatestVersion}", "grey"); 112 | OpenAIplugin.VA_Proxy.WriteToLog($"Download here: {PluginDownloadPage}", "grey"); 113 | OpenAIplugin.VA_Proxy.WriteToLog("", "blank"); 114 | 115 | using (SpeechSynthesizer synthesizer = new SpeechSynthesizer()) 116 | { 117 | synthesizer.Volume = 65; 118 | synthesizer.Rate = 1; 119 | synthesizer.Speak("Please update the OpenAI Plugin for VoiceAttack to the latest version."); 120 | } 121 | 122 | } 123 | catch (Exception ex) 124 | { 125 | Logging.WriteToLog_Long($"OpenAI Plugin Error: {ex.Message}", "red"); 126 | } 127 | } 128 | 129 | } 130 | } 131 | --------------------------------------------------------------------------------