Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A lot of improvements #15

Merged
merged 11 commits into from
Nov 16, 2023
Merged
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.100-rc.2",
"version": "8.0.100",
"rollForward": "latestFeature",
"allowPrerelease": true
}
Expand Down
1 change: 1 addition & 0 deletions src/ChatPrisma/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ResourceDictionary Source="/Themes/ButtonStyles.xaml" />
<ResourceDictionary Source="/Themes/TextBoxStyles.xaml" />
<ResourceDictionary Source="/Themes/ScrollBarStyles.xaml" />
<ResourceDictionary Source="/Themes/CheckBoxStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Expand Down
10 changes: 6 additions & 4 deletions src/ChatPrisma/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using ChatPrisma.Services.KeyboardHooks;
using ChatPrisma.Services.TextExtractor;
using ChatPrisma.Services.TextWriter;
using ChatPrisma.Services.UpdateOptions;
using ChatPrisma.Services.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -98,7 +99,7 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
o.AppShutdownHeader = "Beenden";
});
services.AddOptions<OpenAIOptions>()
.BindConfiguration("OpenAI")
.BindConfiguration(OpenAIOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddOptions<ApplicationOptions>()
Expand All @@ -123,7 +124,7 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
o.CheckForUpdatesInBackground = true;
o.MinutesBetweenUpdateChecks = 30;
})
.BindConfiguration("Updater")
.BindConfiguration(UpdaterOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddOptions<HotkeyOptions>()
Expand All @@ -134,15 +135,15 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
o.HotkeyDelayInMilliseconds = 500;
o.ClipboardDelayInMilliseconds = 500;
})
.BindConfiguration("Hotkey")
.BindConfiguration(HotkeyOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddOptions<TextEnhancementOptions>()
.Configure(o =>
{
o.TextSize = 12;
})
.BindConfiguration("TextEnhancement")
.BindConfiguration(TextEnhancementOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart();

Expand All @@ -166,6 +167,7 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
services.AddSingleton<IPackageExtractor, ZipPackageExtractor>();
services.AddSingleton<IUpdateManager, UpdateManager>();
services.AddSingleton<IAutoStartService, RegistryAutoStartService>();
services.AddSingleton<IUpdateOptionsService, UpdateOptionsService>();

// Hosted Services
services.AddHostedService<StartKeyboardHooksHostedService>();
Expand Down
15 changes: 15 additions & 0 deletions src/ChatPrisma/ChatPrisma.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>Themes\Images\AppIcon.ico</ApplicationIcon>
<UserSecretsId>Chat-Prisma-9c00c175-581f-4d2e-a2f2-5e67274af4d4</UserSecretsId>
<ApplicationManifest>app.manifest</ApplicationManifest>
<NoWarn>$(NoWarn);WFAC010</NoWarn>
</PropertyGroup>

<ItemGroup Label="Dependencies">
Expand All @@ -28,6 +30,19 @@
<ItemGroup Label="Versioning">
<PackageReference Include="Nerdbank.GitVersioning" Version="3.6.133" PrivateAssets="all" />
</ItemGroup>
<Target Name="AppManifestUpdate"
DependsOnTargets="GetBuildVersion"
BeforeTargets="CoreCompile"
Condition="'$(Configuration)' == 'Release'">
<PropertyGroup>
<AppManifestContents>$([System.IO.File]::ReadAllText('app.manifest'))</AppManifestContents>
<UpdatedAppManifestContents>$([System.Text.RegularExpressions.Regex]::Replace($(AppManifestContents), '\d+\.\d+\.\d+\.\d+', $(AssemblyVersion)))</UpdatedAppManifestContents>
</PropertyGroup>

<WriteLinesToFile File="app.manifest"
Lines="$(UpdatedAppManifestContents)"
Overwrite="true" />
</Target>

<ItemGroup Label="Config">
<None Update="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
Expand Down
23 changes: 23 additions & 0 deletions src/ChatPrisma/Common/KeyboardHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Windows.Input;

namespace ChatPrisma.Common;

public static class KeyboardHelper
{
private static readonly Key[] s_allKeys = Enum.GetValues<Key>();

public static bool AnyKeyPressed()
{
foreach (var key in s_allKeys)
{
// Skip the None key
if (key == Key.None)
continue;

if (Keyboard.IsKeyDown(key))
return true;
}

return false;
}
}
33 changes: 33 additions & 0 deletions src/ChatPrisma/Common/TaskHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace ChatPrisma.Common;

public static class TaskHelper
{
public static async Task<bool> WaitUntil(Func<bool> condition, int timeoutInMilliseconds)
{
var result = await WaitUntil(() => (condition(), string.Empty), timeoutInMilliseconds);
return result.Success;
}
public static async Task<(bool Success, T? Data)> WaitUntil<T>(Func<(bool, T)> condition, int timeoutInMilliseconds)
{
using var cancellationTokenSource = new CancellationTokenSource(timeoutInMilliseconds);
var cancellationToken = cancellationTokenSource.Token;

try
{
// Wait until we reach the timeout or condition is true
while (true)
{
var (success, data) = condition();
if (success)
return (true, data);

// This delay is cancellable and will throw an exception if the token is cancelled.
await Task.Delay(10, cancellationToken);
}
}
catch (TaskCanceledException)
{
return (Success: false, Data: default);
}
}
}
2 changes: 1 addition & 1 deletion src/ChatPrisma/Options/ApplicationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace ChatPrisma.Options;

public class ApplicationOptions
public record ApplicationOptions
{
[Required]
public string ApplicationName { get; set; } = default!;
Expand Down
21 changes: 11 additions & 10 deletions src/ChatPrisma/Options/HotkeyOptions.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using System.ComponentModel.DataAnnotations;

namespace ChatPrisma.Options
namespace ChatPrisma.Options;

public record HotkeyOptions
{
public class HotkeyOptions
{
[Required]
public string Key { get; set; } = default!;
[Required]
public string KeyModifiers { get; set; } = default!;
public int HotkeyDelayInMilliseconds { get; set; }
public int ClipboardDelayInMilliseconds { get; set; }
}
public const string Section = "Hotkey";

[Required]
public string Key { get; set; } = default!;
[Required]
public string KeyModifiers { get; set; } = default!;
public int HotkeyDelayInMilliseconds { get; set; }
public int ClipboardDelayInMilliseconds { get; set; }
}
10 changes: 5 additions & 5 deletions src/ChatPrisma/Options/OpenAIOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

namespace ChatPrisma.Options;

public class OpenAIOptions
public record OpenAIOptions
{
[Required]
public string Model { get; set; } = default!;
public const string Section = "OpenAI";

[Required]
public string ApiKey { get; set; } = default!;
public string? Model { get; set; }

public string? ApiKey { get; set; }
}
4 changes: 3 additions & 1 deletion src/ChatPrisma/Options/TextEnhancementOptions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
namespace ChatPrisma.Options;

public class TextEnhancementOptions
public record TextEnhancementOptions
{
public const string Section = "TextEnhancement";

public int TextSize { get; set; }
public string? CustomInstructions { get; set; }
}
4 changes: 3 additions & 1 deletion src/ChatPrisma/Options/UpdaterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace ChatPrisma.Options;

public class UpdaterOptions
public record UpdaterOptions
{
public const string Section = "Updater";

[Required]
public string GitHubUsername { get; set; } = default!;
[Required]
Expand Down
30 changes: 27 additions & 3 deletions src/ChatPrisma/Services/ChatBot/OpenAIChatBotService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,21 @@ namespace ChatPrisma.Services.ChatBot;

public class OpenAIChatBotService(IOptionsMonitor<OpenAIOptions> openAiConfig, ILogger<OpenAIChatBotService> logger) : IChatBotService
{
private readonly OpenAIClient _client = new(openAiConfig.CurrentValue.ApiKey ?? throw new PrismaException("OpenAI API Key is missing"));

public async IAsyncEnumerable<string> GetResponse(List<PrismaChatMessage> messages, [EnumeratorCancellation] CancellationToken token = default)
{
var client = this.GetClient();
if (client is null)
{
yield return "Bitte tragen Sie einen OpenAI API-Key in den Einstellungen ein.";
yield break;
}

if (string.IsNullOrWhiteSpace(openAiConfig.CurrentValue.Model))
{
yield return "Bitte tragen Sie ein OpenAI Model in den Einstellungen ein.";
yield break;
}

var chatCompletionsOptions = new ChatCompletionsOptions();
foreach (var message in messages)
{
Expand All @@ -20,7 +31,7 @@ public async IAsyncEnumerable<string> GetResponse(List<PrismaChatMessage> messag

logger.LogInformation("Calling ChatGPT model {Model}", openAiConfig.CurrentValue.Model);

var response = await this._client.GetChatCompletionsStreamingAsync(openAiConfig.CurrentValue.Model, chatCompletionsOptions, token);
var response = await client.GetChatCompletionsStreamingAsync(openAiConfig.CurrentValue.Model, chatCompletionsOptions, token);

using var completions = response.Value;

Expand All @@ -46,4 +57,17 @@ private ChatMessage ConvertChatMessage(PrismaChatMessage message)

return new ChatMessage(openAiChatRole, message.Content);
}

private (OpenAIClient Client, string ApiKey)? _lastClient;
private OpenAIClient? GetClient()
{
if (this._lastClient is null || this._lastClient.Value.ApiKey != openAiConfig.CurrentValue.ApiKey)
{
_lastClient = string.IsNullOrWhiteSpace(openAiConfig.CurrentValue.ApiKey) is false
? (new OpenAIClient(openAiConfig.CurrentValue.ApiKey), openAiConfig.CurrentValue.ApiKey)
: null;
}

return _lastClient?.Client;
}
}
9 changes: 8 additions & 1 deletion src/ChatPrisma/Services/Dialogs/DialogService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,14 @@ public class DialogService(IServiceProvider serviceProvider, IOptionsMonitor<App
// Need an await for the caller to yield
await Task.Yield();

return window.ShowDialog();
var result = window.ShowDialog();

if (viewModel is IFinalize finalize)
{
await finalize.FinalizeAsync();
}

return result;
}

private Type ResolveViewType(object viewModel)
Expand Down
6 changes: 6 additions & 0 deletions src/ChatPrisma/Services/Dialogs/IFinalize.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ChatPrisma.Services.Dialogs;

public interface IFinalize
{
Task FinalizeAsync();
}
Loading