diff --git a/global.json b/global.json
index a40d106..d6c191f 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.100-rc.2",
+ "version": "8.0.100",
"rollForward": "latestFeature",
"allowPrerelease": true
}
diff --git a/src/ChatPrisma/App.xaml b/src/ChatPrisma/App.xaml
index e23678a..2e42563 100644
--- a/src/ChatPrisma/App.xaml
+++ b/src/ChatPrisma/App.xaml
@@ -16,6 +16,7 @@
+
diff --git a/src/ChatPrisma/App.xaml.cs b/src/ChatPrisma/App.xaml.cs
index 4bf04b7..1e86e28 100644
--- a/src/ChatPrisma/App.xaml.cs
+++ b/src/ChatPrisma/App.xaml.cs
@@ -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;
@@ -98,7 +99,7 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
o.AppShutdownHeader = "Beenden";
});
services.AddOptions()
- .BindConfiguration("OpenAI")
+ .BindConfiguration(OpenAIOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddOptions()
@@ -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()
@@ -134,7 +135,7 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
o.HotkeyDelayInMilliseconds = 500;
o.ClipboardDelayInMilliseconds = 500;
})
- .BindConfiguration("Hotkey")
+ .BindConfiguration(HotkeyOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddOptions()
@@ -142,7 +143,7 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
{
o.TextSize = 12;
})
- .BindConfiguration("TextEnhancement")
+ .BindConfiguration(TextEnhancementOptions.Section)
.ValidateDataAnnotations()
.ValidateOnStart();
@@ -166,6 +167,7 @@ private IHostBuilder CreateHostBuilder(string[] args) => Microsoft.Extensions.Ho
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
// Hosted Services
services.AddHostedService();
diff --git a/src/ChatPrisma/ChatPrisma.csproj b/src/ChatPrisma/ChatPrisma.csproj
index af8382e..2e49fc3 100644
--- a/src/ChatPrisma/ChatPrisma.csproj
+++ b/src/ChatPrisma/ChatPrisma.csproj
@@ -7,6 +7,8 @@
true
Themes\Images\AppIcon.ico
Chat-Prisma-9c00c175-581f-4d2e-a2f2-5e67274af4d4
+ app.manifest
+ $(NoWarn);WFAC010
@@ -28,6 +30,19 @@
+
+
+ $([System.IO.File]::ReadAllText('app.manifest'))
+ $([System.Text.RegularExpressions.Regex]::Replace($(AppManifestContents), '\d+\.\d+\.\d+\.\d+', $(AssemblyVersion)))
+
+
+
+
diff --git a/src/ChatPrisma/Common/KeyboardHelper.cs b/src/ChatPrisma/Common/KeyboardHelper.cs
new file mode 100644
index 0000000..ddac09f
--- /dev/null
+++ b/src/ChatPrisma/Common/KeyboardHelper.cs
@@ -0,0 +1,23 @@
+using System.Windows.Input;
+
+namespace ChatPrisma.Common;
+
+public static class KeyboardHelper
+{
+ private static readonly Key[] s_allKeys = Enum.GetValues();
+
+ 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;
+ }
+}
diff --git a/src/ChatPrisma/Common/TaskHelper.cs b/src/ChatPrisma/Common/TaskHelper.cs
new file mode 100644
index 0000000..9414962
--- /dev/null
+++ b/src/ChatPrisma/Common/TaskHelper.cs
@@ -0,0 +1,33 @@
+namespace ChatPrisma.Common;
+
+public static class TaskHelper
+{
+ public static async Task WaitUntil(Func condition, int timeoutInMilliseconds)
+ {
+ var result = await WaitUntil(() => (condition(), string.Empty), timeoutInMilliseconds);
+ return result.Success;
+ }
+ public static async Task<(bool Success, T? Data)> WaitUntil(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);
+ }
+ }
+}
diff --git a/src/ChatPrisma/Options/ApplicationOptions.cs b/src/ChatPrisma/Options/ApplicationOptions.cs
index df38791..7242a4e 100644
--- a/src/ChatPrisma/Options/ApplicationOptions.cs
+++ b/src/ChatPrisma/Options/ApplicationOptions.cs
@@ -2,7 +2,7 @@
namespace ChatPrisma.Options;
-public class ApplicationOptions
+public record ApplicationOptions
{
[Required]
public string ApplicationName { get; set; } = default!;
diff --git a/src/ChatPrisma/Options/HotkeyOptions.cs b/src/ChatPrisma/Options/HotkeyOptions.cs
index f0c94e0..217ef29 100644
--- a/src/ChatPrisma/Options/HotkeyOptions.cs
+++ b/src/ChatPrisma/Options/HotkeyOptions.cs
@@ -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; }
}
diff --git a/src/ChatPrisma/Options/OpenAIOptions.cs b/src/ChatPrisma/Options/OpenAIOptions.cs
index aa89e8f..b291fae 100644
--- a/src/ChatPrisma/Options/OpenAIOptions.cs
+++ b/src/ChatPrisma/Options/OpenAIOptions.cs
@@ -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; }
}
diff --git a/src/ChatPrisma/Options/TextEnhancementOptions.cs b/src/ChatPrisma/Options/TextEnhancementOptions.cs
index 03a4b6f..c95f34d 100644
--- a/src/ChatPrisma/Options/TextEnhancementOptions.cs
+++ b/src/ChatPrisma/Options/TextEnhancementOptions.cs
@@ -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; }
}
diff --git a/src/ChatPrisma/Options/UpdaterOptions.cs b/src/ChatPrisma/Options/UpdaterOptions.cs
index 06666f9..048f83d 100644
--- a/src/ChatPrisma/Options/UpdaterOptions.cs
+++ b/src/ChatPrisma/Options/UpdaterOptions.cs
@@ -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]
diff --git a/src/ChatPrisma/Services/ChatBot/OpenAIChatBotService.cs b/src/ChatPrisma/Services/ChatBot/OpenAIChatBotService.cs
index d3168ac..a88a5e1 100644
--- a/src/ChatPrisma/Services/ChatBot/OpenAIChatBotService.cs
+++ b/src/ChatPrisma/Services/ChatBot/OpenAIChatBotService.cs
@@ -8,10 +8,21 @@ namespace ChatPrisma.Services.ChatBot;
public class OpenAIChatBotService(IOptionsMonitor openAiConfig, ILogger logger) : IChatBotService
{
- private readonly OpenAIClient _client = new(openAiConfig.CurrentValue.ApiKey ?? throw new PrismaException("OpenAI API Key is missing"));
-
public async IAsyncEnumerable GetResponse(List 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)
{
@@ -20,7 +31,7 @@ public async IAsyncEnumerable GetResponse(List 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;
@@ -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;
+ }
}
diff --git a/src/ChatPrisma/Services/Dialogs/DialogService.cs b/src/ChatPrisma/Services/Dialogs/DialogService.cs
index ff6b022..c6c92f8 100644
--- a/src/ChatPrisma/Services/Dialogs/DialogService.cs
+++ b/src/ChatPrisma/Services/Dialogs/DialogService.cs
@@ -54,7 +54,14 @@ public class DialogService(IServiceProvider serviceProvider, IOptionsMonitor hotkeyOptions
private async Task WaitUntilNoKeyPressed()
{
- var task = Task.Delay(TimeSpan.FromMilliseconds(hotkeyOptions.CurrentValue.HotkeyDelayInMilliseconds));
var watch = Stopwatch.StartNew();
- // Either wait until the task is completed or the user releases all keys
- while (task.IsCompleted is false)
+ var success = await TaskHelper.WaitUntil(() => KeyboardHelper.AnyKeyPressed() is false, hotkeyOptions.CurrentValue.HotkeyDelayInMilliseconds);
+ if (success)
{
- if (AnyKeyPressed() is false)
- {
- logger.LogInformation("Early exit from WaitUntilNoKeyPressed because no key is pressed anymore (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
- return;
- }
-
- await Task.Delay(10);
+ logger.LogInformation("Early exit from WaitUntilNoKeyPressed because no key is pressed anymore (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
}
-
- logger.LogInformation("Sadly the user did not release all keys in time (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
- }
-
- private static readonly Key[] s_allKeys = Enum.GetValues();
- private static bool AnyKeyPressed()
- {
- foreach (var key in s_allKeys)
+ else
{
- // Skip the None key
- if (key == Key.None)
- continue;
-
- if (Keyboard.IsKeyDown(key))
- return true;
+ logger.LogInformation("Sadly the user did not release all keys in time (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
}
-
- return false;
}
private async Task WaitUntilClipboardTextIsAvailable()
{
- var task = Task.Delay(TimeSpan.FromMilliseconds(hotkeyOptions.CurrentValue.ClipboardDelayInMilliseconds));
var watch = Stopwatch.StartNew();
- // Either wait until the task is completed or we got some text in the clipboard
- while (task.IsCompleted is false)
+ var (success, text) = await TaskHelper.WaitUntil(ClipboardHasText, hotkeyOptions.CurrentValue.ClipboardDelayInMilliseconds);
+ if (success)
{
- var dataObject = Clipboard.GetDataObject();
- if (dataObject?.GetData(DataFormats.Text) is string text)
+ logger.LogInformation("Early exit from WaitUntilClipboardIsFilled because we got some text from the clipboard (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
+ }
+ else
+ {
+ logger.LogInformation("Sadly no text available in clipboard (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
+ }
+
+ return text;
+
+ (bool, string?) ClipboardHasText()
+ {
+ try
{
- logger.LogInformation("Early exit from WaitUntilClipboardIsFilled because we got some text from the clipboard (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
- return text;
+ var dataObject = Clipboard.GetDataObject();
+ return dataObject?.GetData(DataFormats.Text) is string s
+ ? (true, s)
+ : (false, null);
+ }
+#pragma warning disable CA1031
+ catch (Exception e)
+#pragma warning restore CA1031
+ {
+ logger.LogError(e, "An error occurred when accessing the clipboard contents.");
+ return (false, null);
}
-
- await Task.Delay(10);
}
-
- logger.LogInformation("Sadly no text available in clipboard (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
- return null;
}
}
diff --git a/src/ChatPrisma/Services/TextWriter/SendKeysClipboardTextWriter.cs b/src/ChatPrisma/Services/TextWriter/SendKeysClipboardTextWriter.cs
index d1e11bc..d247363 100644
--- a/src/ChatPrisma/Services/TextWriter/SendKeysClipboardTextWriter.cs
+++ b/src/ChatPrisma/Services/TextWriter/SendKeysClipboardTextWriter.cs
@@ -1,8 +1,13 @@
+using System.Diagnostics;
using System.Windows.Forms;
+using ChatPrisma.Common;
+using ChatPrisma.Options;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
namespace ChatPrisma.Services.TextWriter;
-public class SendKeysClipboardTextWriter : IClipboardTextWriter
+public class SendKeysClipboardTextWriter(IOptionsMonitor hotkeyOptions, ILogger logger) : IClipboardTextWriter
{
public async Task CopyTextAsync(string text, bool autoPaste)
{
@@ -10,9 +15,25 @@ public async Task CopyTextAsync(string text, bool autoPaste)
if (autoPaste)
{
+ await this.WaitUntilNoKeyPressed();
SendKeys.SendWait("^v");
}
await Task.CompletedTask;
}
+
+ private async Task WaitUntilNoKeyPressed()
+ {
+ var watch = Stopwatch.StartNew();
+
+ var success = await TaskHelper.WaitUntil(() => KeyboardHelper.AnyKeyPressed() is false, hotkeyOptions.CurrentValue.HotkeyDelayInMilliseconds);
+ if (success)
+ {
+ logger.LogInformation("Early exit from WaitUntilNoKeyPressed because no key is pressed anymore (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
+ }
+ else
+ {
+ logger.LogInformation("Sadly the user did not release all keys in time (after {Time} ms)", watch.Elapsed.TotalMilliseconds);
+ }
+ }
}
diff --git a/src/ChatPrisma/Services/UpdateOptions/IUpdateOptionsService.cs b/src/ChatPrisma/Services/UpdateOptions/IUpdateOptionsService.cs
new file mode 100644
index 0000000..cbcfbb9
--- /dev/null
+++ b/src/ChatPrisma/Services/UpdateOptions/IUpdateOptionsService.cs
@@ -0,0 +1,8 @@
+using ChatPrisma.Options;
+
+namespace ChatPrisma.Services.UpdateOptions;
+
+public interface IUpdateOptionsService
+{
+ Task Update(OpenAIOptions options);
+}
diff --git a/src/ChatPrisma/Services/UpdateOptions/UpdateOptionsService.cs b/src/ChatPrisma/Services/UpdateOptions/UpdateOptionsService.cs
new file mode 100644
index 0000000..217387d
--- /dev/null
+++ b/src/ChatPrisma/Services/UpdateOptions/UpdateOptionsService.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using ChatPrisma.Options;
+using Microsoft.Extensions.Hosting;
+
+namespace ChatPrisma.Services.UpdateOptions;
+
+public class UpdateOptionsService(IHostEnvironment hostEnvironment) : IUpdateOptionsService
+{
+ public async Task Update(OpenAIOptions options)
+ {
+ await this.UpdateSettings(settings =>
+ {
+ var newJson = JsonSerializer.Serialize(options);
+ settings[OpenAIOptions.Section] = JsonNode.Parse(newJson);
+ });
+ }
+
+ private async Task UpdateSettings(Action updateAction)
+ {
+ var path = Path.Combine(AppContext.BaseDirectory, $"appsettings.{hostEnvironment.EnvironmentName}.json");
+
+ string settingsContent;
+ try
+ {
+ settingsContent = await File.ReadAllTextAsync(path);
+ }
+ catch (FileNotFoundException)
+ {
+ settingsContent = "{}";
+ }
+
+ var json = JsonNode.Parse(settingsContent) ?? new JsonObject();
+
+ updateAction(json);
+
+ await using var writer = File.CreateText(path);
+ await writer.WriteAsync(json.ToJsonString(new JsonSerializerOptions
+ {
+ WriteIndented = true
+ }));
+ }
+}
diff --git a/src/ChatPrisma/Themes/ButtonStyles.xaml b/src/ChatPrisma/Themes/ButtonStyles.xaml
index 7b53cd6..1b36f46 100644
--- a/src/ChatPrisma/Themes/ButtonStyles.xaml
+++ b/src/ChatPrisma/Themes/ButtonStyles.xaml
@@ -3,7 +3,7 @@
+
\ No newline at end of file
diff --git a/src/ChatPrisma/Themes/Images/AppIcon.ico b/src/ChatPrisma/Themes/Images/AppIcon.ico
index 4040c6b..18930a1 100644
Binary files a/src/ChatPrisma/Themes/Images/AppIcon.ico and b/src/ChatPrisma/Themes/Images/AppIcon.ico differ
diff --git a/src/ChatPrisma/Themes/Images/AppIcon_Source.png b/src/ChatPrisma/Themes/Images/AppIcon_Source.png
new file mode 100644
index 0000000..d9b44a9
Binary files /dev/null and b/src/ChatPrisma/Themes/Images/AppIcon_Source.png differ
diff --git a/src/ChatPrisma/Views/Settings/SettingsView.xaml b/src/ChatPrisma/Views/Settings/SettingsView.xaml
index 2571732..b354e9e 100644
--- a/src/ChatPrisma/Views/Settings/SettingsView.xaml
+++ b/src/ChatPrisma/Views/Settings/SettingsView.xaml
@@ -1,8 +1,6 @@
-
-
-
-
-
-
+ Width="400">
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ChatPrisma/Views/Settings/SettingsViewModel.cs b/src/ChatPrisma/Views/Settings/SettingsViewModel.cs
index 1473712..614a06a 100644
--- a/src/ChatPrisma/Views/Settings/SettingsViewModel.cs
+++ b/src/ChatPrisma/Views/Settings/SettingsViewModel.cs
@@ -1,31 +1,39 @@
-using ChatPrisma.Services.AutoStart;
+using ChatPrisma.Options;
+using ChatPrisma.Services.AutoStart;
using ChatPrisma.Services.Dialogs;
+using ChatPrisma.Services.UpdateOptions;
using CommunityToolkit.Mvvm.ComponentModel;
-using CommunityToolkit.Mvvm.Input;
+using Microsoft.Extensions.Options;
namespace ChatPrisma.Views.Settings;
-public partial class SettingsViewModel(IAutoStartService autoStartService) : ObservableObject, IInitialize
+public partial class SettingsViewModel(IAutoStartService autoStartService, IUpdateOptionsService updateOptionsService, IOptionsMonitor applicationOptions, IOptionsMonitor openAIOptions) : ObservableObject, IInitialize, IFinalize
{
[ObservableProperty]
private bool _isAutoStartActive;
+ [ObservableProperty]
+ private string _applicationName = applicationOptions.CurrentValue.ApplicationName;
+
+ [ObservableProperty]
+ private string? _model = openAIOptions.CurrentValue.Model;
+
+ [ObservableProperty]
+ private string? _apiKey = openAIOptions.CurrentValue.ApiKey;
+
public async Task InitializeAsync()
{
this.IsAutoStartActive = await autoStartService.IsInAutoStart();
}
- [RelayCommand]
- private async Task EnableAutoStart()
+ public async Task FinalizeAsync()
{
- await autoStartService.SetAutoStart(true);
- this.IsAutoStartActive = true;
- }
+ await autoStartService.SetAutoStart(this.IsAutoStartActive);
- [RelayCommand]
- private async Task DisableAutoStart()
- {
- await autoStartService.SetAutoStart(false);
- this.IsAutoStartActive = false;
+ await updateOptionsService.Update(openAIOptions.CurrentValue with
+ {
+ Model = this.Model,
+ ApiKey = this.ApiKey
+ });
}
}
diff --git a/src/ChatPrisma/Views/TextEnhancement/TextEnhancementView.xaml b/src/ChatPrisma/Views/TextEnhancement/TextEnhancementView.xaml
index 59fc2ac..581b642 100644
--- a/src/ChatPrisma/Views/TextEnhancement/TextEnhancementView.xaml
+++ b/src/ChatPrisma/Views/TextEnhancement/TextEnhancementView.xaml
@@ -4,16 +4,12 @@
xmlns:local="clr-namespace:ChatPrisma.Views.TextEnhancement"
xmlns:emoji="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf"
xmlns:themes="clr-namespace:ChatPrisma.Themes"
+ xmlns:wpf="clr-namespace:FluentIcons.WPF;assembly=FluentIcons.WPF"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
- Width="600"
-
- MinHeight="200"
- MaxHeight="800"
-
d:DataContext="{d:DesignInstance local:TextEnhancementViewModel}"
Loaded="TextEnhancementView_OnLoaded"
@@ -29,12 +25,13 @@
-
+
+
-
@@ -50,10 +47,33 @@
-
+
+
+
+
+
+
+
+
@@ -72,7 +92,7 @@
diff --git a/src/ChatPrisma/Views/TextEnhancement/TextEnhancementView.xaml.cs b/src/ChatPrisma/Views/TextEnhancement/TextEnhancementView.xaml.cs
index d9b9b92..030ee2c 100644
--- a/src/ChatPrisma/Views/TextEnhancement/TextEnhancementView.xaml.cs
+++ b/src/ChatPrisma/Views/TextEnhancement/TextEnhancementView.xaml.cs
@@ -1,6 +1,7 @@
-using System.Windows;
+using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
+using System.Windows.Media;
using System.Windows.Threading;
namespace ChatPrisma.Views.TextEnhancement;
@@ -16,15 +17,47 @@ private void TextEnhancementView_OnLoaded(object sender, RoutedEventArgs e)
// Ensure we are scrolled to the bottom
window.Dispatcher.BeginInvoke(DispatcherPriority.Render, this.ScrollToBottom);
- // Place window slightly to the top
+ // Setup dimensions and starting position
window.Dispatcher.BeginInvoke(DispatcherPriority.Render, () =>
{
var helper = new WindowInteropHelper(window);
var currentScreen = Screen.FromHandle(helper.Handle);
- var currentScreenHeight = currentScreen.Bounds.Height;
+ var dpi = VisualTreeHelper.GetDpi(window);
+ var currentScreenWorkingAreaDpiAdjusted = new Rectangle(
+ (int)(currentScreen.WorkingArea.X / dpi.DpiScaleX),
+ (int)(currentScreen.WorkingArea.Y / dpi.DpiScaleY),
+ (int)(currentScreen.WorkingArea.Width / dpi.DpiScaleX),
+ (int)(currentScreen.WorkingArea.Height / dpi.DpiScaleY));
- // Place the window a bit moved to the top, so it is perfectly centered if we reach this.MaxHeight
- window.Top = Math.Max((currentScreenHeight - this.MaxHeight) / 2, 0);
+ this.Width = currentScreenWorkingAreaDpiAdjusted.Width switch
+ {
+ > 1400 => 800,
+ _ => 600
+ };
+
+ this.MinHeight = 200;
+
+ // Set window max-height, so we don't have to think about the window-shell when calculating the starting position
+ window.MaxHeight = currentScreenWorkingAreaDpiAdjusted.Height switch
+ {
+ > 1200 => 1000,
+ > 1000 => 800,
+ > 700 => 600,
+ _ => 400,
+ };
+
+ // Place the window a bit moved to the top, so it is perfectly centered if we reach window.MaxHeight
+ window.Top = currentScreenWorkingAreaDpiAdjusted.Y + Math.Max((currentScreenWorkingAreaDpiAdjusted.Height - window.MaxHeight) / 2, 0);
+ // And horizontally perfectly centered, because we have a fixed width
+ window.Left = currentScreenWorkingAreaDpiAdjusted.X + Math.Max((currentScreenWorkingAreaDpiAdjusted.Width - this.Width) / 2, 0);
+ });
+
+ // Hide the window until the previous call has positioned it correctly
+ // Don't use Visibility here, as that will not just hide the window, but also deactivate it and make it "not be a dialog anymore"
+ window.Opacity = 0;
+ window.Dispatcher.BeginInvoke(DispatcherPriority.Render, () =>
+ {
+ window.Opacity = 1;
});
// Ensure window is shown above all other windows
diff --git a/src/ChatPrisma/app.manifest b/src/ChatPrisma/app.manifest
new file mode 100644
index 0000000..93f2383
--- /dev/null
+++ b/src/ChatPrisma/app.manifest
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PerMonitor
+ true
+
+ true
+
+
+
+