├── .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 |
--------------------------------------------------------------------------------