diff --git a/highminded.csproj b/highminded.csproj
index 241e9f7..701e24e 100644
--- a/highminded.csproj
+++ b/highminded.csproj
@@ -27,14 +27,15 @@
None
All
+
-
+
diff --git a/models/ChatViewModel.cs b/models/ChatViewModel.cs
new file mode 100644
index 0000000..47cd1a5
--- /dev/null
+++ b/models/ChatViewModel.cs
@@ -0,0 +1,9 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace highminded.models;
+
+public partial class ChatViewModel : ObservableObject
+{
+ [ObservableProperty] private string _content = "";
+ [ObservableProperty] private string _prompt = "";
+}
\ No newline at end of file
diff --git a/models/MainViewModel.cs b/models/MainViewModel.cs
new file mode 100644
index 0000000..9c9cf9d
--- /dev/null
+++ b/models/MainViewModel.cs
@@ -0,0 +1,13 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace highminded.models;
+
+public partial class MainViewModel : ObservableObject
+{
+ [ObservableProperty] private string _appName = "High Minded";
+ [ObservableProperty] private string _hideShortcutKey = "SHIFT + \\";
+ [ObservableProperty] private string _screenshotShortcutKey = "SHIFT + S";
+ [ObservableProperty] private string _audioShortcutKey = "SHIFT + A";
+ [ObservableProperty] private bool _isRecording = false;
+ [ObservableProperty] private bool _isHidden = true;
+}
\ No newline at end of file
diff --git a/models/SettingsViewModel.cs b/models/SettingsViewModel.cs
new file mode 100644
index 0000000..ea1cffd
--- /dev/null
+++ b/models/SettingsViewModel.cs
@@ -0,0 +1,12 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace highminded.models;
+
+public partial class SettingsViewModel : ObservableObject
+{
+ [ObservableProperty] private string _model = "";
+ [ObservableProperty] private string _apiUrl = "";
+ [ObservableProperty] private string _apiKey = "";
+ [ObservableProperty] private string _screenshotPrompt = "";
+ [ObservableProperty] private string _audioPrompt = "";
+}
\ No newline at end of file
diff --git a/ui/controls/ChatUserControl.axaml b/ui/controls/ChatUserControl.axaml
index dc11dc7..735954e 100644
--- a/ui/controls/ChatUserControl.axaml
+++ b/ui/controls/ChatUserControl.axaml
@@ -2,14 +2,16 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:vm="using:highminded.models"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="highminded.ui.controls.ChatUserControl">
+ x:Class="highminded.ui.controls.ChatUserControl"
+ x:DataType="vm:ChatViewModel">
-
-
+
+
\ No newline at end of file
diff --git a/ui/controls/ChatUserControl.axaml.cs b/ui/controls/ChatUserControl.axaml.cs
index 6fdadfc..ac99f1a 100644
--- a/ui/controls/ChatUserControl.axaml.cs
+++ b/ui/controls/ChatUserControl.axaml.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
+using highminded.models;
using highminded.utils;
using OpenAI.Chat;
using Markdig;
@@ -16,14 +17,18 @@ namespace highminded.ui.controls;
public partial class ChatUserControl : UserControl
{
- private readonly MarkdownPipeline _pipeline = null!;
- private AudioCapture _audioCapture = null!;
+ private readonly ChatViewModel _model;
+ private readonly MarkdownPipeline _pipeline;
+ private readonly AudioCapture _audioCapture;
public ChatUserControl()
{
InitializeComponent();
- _pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseColorCode().Build();
+ DataContext = InMemoryDb.Obj.ChatViewModel;
+
_audioCapture = new AudioCapture();
+ _model = InMemoryDb.Obj.ChatViewModel;
+ _pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseColorCode().Build();
}
public void StartRecord()
@@ -33,42 +38,15 @@ public partial class ChatUserControl : UserControl
var dirPath = Path.Combine(Environment.CurrentDirectory, "audio");
var filePath = Path.Combine(dirPath, fileName);
Directory.CreateDirectory(dirPath);
+ InMemoryDb.Obj.MainViewModel.IsRecording = true;
_audioCapture.StartRecording(filePath);
}
public void StopRecord()
{
- if (_audioCapture != null)
- {
- _audioCapture.StopRecording();
- SendAudio();
- }
- }
-
- public async void SendAudio()
- {
- try
- {
- var dirPath = Path.Combine(Environment.CurrentDirectory, "audio");
- var latestAudio = new DirectoryInfo(dirPath).GetFiles().OrderByDescending(f => f.LastWriteTime).First();
- var filePath = Path.Combine(dirPath, latestAudio.Name);
- var audioFileBytes = await File.ReadAllBytesAsync(filePath);
- var audioBytes = BinaryData.FromBytes(audioFileBytes);
-
- List messages =
- [
- new UserChatMessage(
- ChatMessageContentPart.CreateTextPart(InMemoryDb.Obj.SettingsManager.Settings.AudioPrompt),
- ChatMessageContentPart.CreateInputAudioPart(audioBytes, ChatInputAudioFormat.Wav)
- )
- ];
-
- await ProcessChatStreamAsync(messages);
- }
- catch (Exception err)
- {
- ResultBlock.Text = err.Message;
- }
+ _audioCapture.StopRecording();
+ InMemoryDb.Obj.MainViewModel.IsRecording = false;
+ SendAudio();
}
public async void SendScreenshot()
@@ -99,7 +77,33 @@ public partial class ChatUserControl : UserControl
}
catch (Exception err)
{
- ResultBlock.Text = err.Message;
+ _model.Content = err.Message;
+ }
+ }
+
+ private async void SendAudio()
+ {
+ try
+ {
+ var dirPath = Path.Combine(Environment.CurrentDirectory, "audio");
+ var latestAudio = new DirectoryInfo(dirPath).GetFiles().OrderByDescending(f => f.LastWriteTime).First();
+ var filePath = Path.Combine(dirPath, latestAudio.Name);
+ var audioFileBytes = await File.ReadAllBytesAsync(filePath);
+ var audioBytes = BinaryData.FromBytes(audioFileBytes);
+
+ List messages =
+ [
+ new UserChatMessage(
+ ChatMessageContentPart.CreateTextPart(InMemoryDb.Obj.SettingsManager.Settings.AudioPrompt),
+ ChatMessageContentPart.CreateInputAudioPart(audioBytes, ChatInputAudioFormat.Wav)
+ )
+ ];
+
+ await ProcessChatStreamAsync(messages);
+ }
+ catch (Exception err)
+ {
+ _model.Content = err.Message;
}
}
@@ -109,15 +113,14 @@ public partial class ChatUserControl : UserControl
{
if (e.Key != Key.Enter) return;
- var prompt = PromptBox.Text;
- if (prompt is null) return;
- PromptBox.Clear();
-
+ var prompt = _model.Prompt;
+ if (prompt == string.Empty) return;
+ _model.Prompt = string.Empty;
await ProcessChatStreamAsync(prompt);
}
catch (Exception err)
{
- ResultBlock.Text = err.Message;
+ _model.Content = err.Message;
}
}
@@ -140,7 +143,7 @@ public partial class ChatUserControl : UserControl
responseBuilder.Append(token);
var html = Markdig.Markdown.ToHtml(responseBuilder.ToString(), _pipeline);
- ResultBlock.Text = html;
+ _model.Content = html;
}
}
}
\ No newline at end of file
diff --git a/ui/controls/SettingsUserControl.axaml b/ui/controls/SettingsUserControl.axaml
index adb69d9..5997fd9 100644
--- a/ui/controls/SettingsUserControl.axaml
+++ b/ui/controls/SettingsUserControl.axaml
@@ -2,39 +2,42 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:vm="using:highminded.models"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="highminded.ui.controls.SettingsUserControl">
+ x:Class="highminded.ui.controls.SettingsUserControl"
+ x:DataType="vm:SettingsViewModel">
+
+
-
-
-
+
-
+
-
+
-
+
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/ui/controls/SettingsUserControl.axaml.cs b/ui/controls/SettingsUserControl.axaml.cs
index 642c3b2..bbcf6ac 100644
--- a/ui/controls/SettingsUserControl.axaml.cs
+++ b/ui/controls/SettingsUserControl.axaml.cs
@@ -9,20 +9,11 @@ public partial class SettingsUserControl : UserControl
public SettingsUserControl()
{
InitializeComponent();
-
- ModelTextBox.Text = InMemoryDb.Obj.SettingsManager.Settings.Model;
- ApiUrlTextBox.Text = InMemoryDb.Obj.SettingsManager.Settings.ApiURL;
- ApiKeyTextBox.Text = InMemoryDb.Obj.SettingsManager.Settings.ApiKey;
- ScreenshotPromptTextBox.Text = InMemoryDb.Obj.SettingsManager.Settings.ScreenshotPrompt;
- AudioPromptTextbox.Text = InMemoryDb.Obj.SettingsManager.Settings.AudioPrompt;
+ DataContext = InMemoryDb.Obj.SettingsViewModel;
}
private void SaveSettingsBtn_OnClick(object? sender, RoutedEventArgs e)
{
- InMemoryDb.Obj.SaveSettings(new AppSettings()
- {
- ApiKey = ApiKeyTextBox.Text, ApiURL = ApiUrlTextBox.Text, Model = ModelTextBox.Text,
- ScreenshotPrompt = ScreenshotPromptTextBox.Text, AudioPrompt = AudioPromptTextbox.Text
- });
+ InMemoryDb.Obj.SaveSettings();
}
}
\ No newline at end of file
diff --git a/ui/windows/MainWindow.axaml b/ui/windows/MainWindow.axaml
index 77f50ba..eff3ad9 100644
--- a/ui/windows/MainWindow.axaml
+++ b/ui/windows/MainWindow.axaml
@@ -3,8 +3,10 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
+ xmlns:vm="using:highminded.models"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="highminded.ui.windows.MainWindow"
+ x:DataType="vm:MainViewModel"
Title="highminded"
Height="600"
Width="800"
@@ -18,10 +20,20 @@
Foreground="#fff"
TransparencyBackgroundFallback="Transparent"
FontSize="16">
+
+
+
+
+
+
- High Minded
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
diff --git a/ui/windows/MainWindow.axaml.cs b/ui/windows/MainWindow.axaml.cs
index 6fd2123..0e97fcb 100644
--- a/ui/windows/MainWindow.axaml.cs
+++ b/ui/windows/MainWindow.axaml.cs
@@ -6,6 +6,7 @@ using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
using highminded.ui.controls;
+using highminded.utils;
using SharpHook;
using SharpHook.Data;
@@ -36,13 +37,14 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();
+ DataContext = InMemoryDb.Obj.MainViewModel;
+
UControl.Content = _chatUserControl;
ChatBtnActive();
HideOverlay();
// Global Hotkey
_hook.KeyPressed += OnKeyPressed;
_hook.RunAsync();
- _chatUserControl.SendAudio();
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
@@ -74,56 +76,6 @@ public partial class MainWindow : Window
SettingsBtn.Background = new SolidColorBrush(Color.FromArgb(25, 255, 255, 255));
}
- private void OnKeyPressed(object? sender, KeyboardHookEventArgs e)
- {
- bool hasCtrl = (e.RawEvent.Mask & EventMask.Ctrl) != EventMask.None;
- bool hasAlt = (e.RawEvent.Mask & EventMask.Alt) != EventMask.None;
- bool hasShift = (e.RawEvent.Mask & EventMask.Shift) != EventMask.None;
- bool hasH = e.Data.KeyCode == KeyCode.VcH;
- bool hasBackslash = e.Data.KeyCode == KeyCode.VcBackslash;
- bool hasA = e.Data.KeyCode == KeyCode.VcA;
- bool hasS = e.Data.KeyCode == KeyCode.VcS;
- bool hasQ = e.Data.KeyCode == KeyCode.VcQ;
-
- if (hasCtrl && hasShift && hasAlt && hasH)
- {
- ShowOverlay();
- }
-
- if (hasShift && hasS)
- {
- Dispatcher.UIThread.Post(() => { _chatUserControl.SendScreenshot(); });
- }
-
- if (hasShift && hasA)
- {
- Dispatcher.UIThread.Post(() => { _chatUserControl.StartRecord(); });
- }
-
- if (hasAlt && hasShift && hasQ)
- {
- Dispatcher.UIThread.Post(() => { Environment.Exit(0); });
- }
-
- if (hasShift && hasBackslash)
- {
- Dispatcher.UIThread.Post(() =>
- {
- if (WindowState == WindowState.Minimized || !IsVisible)
- {
- Show();
- WindowState = WindowState.Normal;
- Activate();
- Topmost = true;
- }
- else
- {
- Hide();
- }
- });
- }
- }
-
private void ShowOverlay()
{
var handle = TryGetPlatformHandle();
@@ -188,4 +140,54 @@ public partial class MainWindow : Window
{
Hide();
}
+
+ private void OnKeyPressed(object? sender, KeyboardHookEventArgs e)
+ {
+ bool hasCtrl = (e.RawEvent.Mask & EventMask.Ctrl) != EventMask.None;
+ bool hasAlt = (e.RawEvent.Mask & EventMask.Alt) != EventMask.None;
+ bool hasShift = (e.RawEvent.Mask & EventMask.Shift) != EventMask.None;
+ bool hasH = e.Data.KeyCode == KeyCode.VcH;
+ bool hasBackslash = e.Data.KeyCode == KeyCode.VcBackslash;
+ bool hasA = e.Data.KeyCode == KeyCode.VcA;
+ bool hasS = e.Data.KeyCode == KeyCode.VcS;
+ bool hasQ = e.Data.KeyCode == KeyCode.VcQ;
+
+ if (hasCtrl && hasShift && hasAlt && hasH)
+ {
+ ShowOverlay();
+ }
+
+ if (hasShift && hasS)
+ {
+ Dispatcher.UIThread.Post(() => { _chatUserControl.SendScreenshot(); });
+ }
+
+ if (hasShift && hasA)
+ {
+ Dispatcher.UIThread.Post(() => { _chatUserControl.StartRecord(); });
+ }
+
+ if (hasAlt && hasShift && hasQ)
+ {
+ Dispatcher.UIThread.Post(() => { Environment.Exit(0); });
+ }
+
+ if (hasShift && hasBackslash)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (WindowState == WindowState.Minimized || !IsVisible)
+ {
+ Show();
+ WindowState = WindowState.Normal;
+ Activate();
+ Topmost = true;
+ }
+ else
+ {
+ Hide();
+ }
+ });
+ }
+ }
}
\ No newline at end of file
diff --git a/utils/AudioCapture.cs b/utils/AudioCapture.cs
index 7eaf5b1..7a3f524 100644
--- a/utils/AudioCapture.cs
+++ b/utils/AudioCapture.cs
@@ -1,51 +1,27 @@
-using NAudio.CoreAudioApi;
-using NAudio.Wave;
+using System.IO;
+using SoundFlow.Backends.MiniAudio;
+using SoundFlow.Components;
+using SoundFlow.Enums;
namespace highminded.utils;
public class AudioCapture
{
- private WasapiLoopbackCapture capture;
- private WaveFileWriter writer;
- private string outputFilePath;
+ private readonly MiniAudioEngine _audioEngine = new(48000, Capability.Loopback);
+ private Stream? _fileStream;
+ private Recorder? _recorder;
- public AudioCapture()
+ public void StartRecording(string filePath)
{
- capture = new WasapiLoopbackCapture();
- }
-
- public void StartRecording(string outPath)
- {
- writer = new WaveFileWriter(outPath, capture.WaveFormat);
- capture.DataAvailable += Capture_DataAvailable;
- capture.RecordingStopped += Capture_RecordingStopped;
- capture.StartRecording();
+ _fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
+ _recorder = new Recorder(_fileStream, sampleRate: 48000);
+ _recorder.StartRecording();
}
public void StopRecording()
{
- capture.StopRecording();
- }
-
- public bool IsRecording()
- {
- if (capture.CaptureState == CaptureState.Capturing || capture.CaptureState == CaptureState.Starting)
- {
- return true;
- }
-
- return false;
- }
-
- private void Capture_DataAvailable(object sender, WaveInEventArgs e)
- {
- writer.Write(e.Buffer, 0, e.BytesRecorded);
- }
-
- private void Capture_RecordingStopped(object sender, StoppedEventArgs e)
- {
- writer.Flush();
- writer.Dispose();
- capture.Dispose();
+ _recorder?.StopRecording();
+ _recorder?.Dispose();
+ _fileStream?.Dispose();
}
}
\ No newline at end of file
diff --git a/utils/InMemoryDB.cs b/utils/InMemoryDB.cs
index 6964685..9f6a069 100644
--- a/utils/InMemoryDB.cs
+++ b/utils/InMemoryDB.cs
@@ -1,5 +1,6 @@
using System;
using System.ClientModel;
+using highminded.models;
using OpenAI;
using OpenAI.Chat;
@@ -7,37 +8,45 @@ namespace highminded.utils;
public class InMemoryDb
{
- internal ChatClient ChatClient;
- internal SettingsManager SettingsManager;
+ public static readonly InMemoryDb Obj = new InMemoryDb();
+
+ internal readonly SettingsManager SettingsManager;
+ internal readonly MainViewModel MainViewModel;
+ internal readonly ChatViewModel ChatViewModel;
+ internal readonly SettingsViewModel SettingsViewModel;
+ internal ChatClient? ChatClient;
// Initialize Singleton Class
private InMemoryDb()
{
- SettingsManager = new SettingsManager();
- if (SettingsManager.Settings.ApiKey != null)
+ SettingsManager = new SettingsManager();
+
+ MainViewModel = new MainViewModel();
+ ChatViewModel = new ChatViewModel();
+ SettingsViewModel = new SettingsViewModel();
+ SettingsViewModel = SettingsManager.Settings;
+
+ if (SettingsManager.Settings.ApiKey != string.Empty)
{
- InitOpenAIClient();
+ InitOpenAiClient();
}
}
- public void InitOpenAIClient()
+ public void SaveSettings()
+ {
+ SettingsManager.Save();
+ InitOpenAiClient();
+ }
+
+ private void InitOpenAiClient()
{
ChatClient = new ChatClient(
model: SettingsManager.Settings.Model,
credential: new ApiKeyCredential(SettingsManager.Settings.ApiKey),
options: new OpenAIClientOptions
{
- Endpoint = new Uri(SettingsManager.Settings.ApiURL)
+ Endpoint = new Uri(SettingsManager.Settings.ApiUrl)
}
);
}
-
- public void SaveSettings(AppSettings settings)
- {
- SettingsManager.Settings = settings;
- SettingsManager.Save();
- InitOpenAIClient();
- }
-
- public static readonly InMemoryDb Obj = new InMemoryDb();
}
\ No newline at end of file
diff --git a/utils/SetttingsManager.cs b/utils/SetttingsManager.cs
index 3179efb..5031634 100644
--- a/utils/SetttingsManager.cs
+++ b/utils/SetttingsManager.cs
@@ -4,27 +4,17 @@ using System.Text.Json;
namespace highminded.utils;
-public class AppSettings
-{
- public string Model { get; set; }
- public string ApiURL { get; set; }
- public string ApiKey { get; set; }
- public string ScreenshotPrompt { get; set; }
- public string AudioPrompt { get; set; }
-
-}
-
public class SettingsManager where T : class, new()
{
private readonly string _settingsPath;
public T Settings { get; internal set; }
- public SettingsManager(string appName = "highminded")
+ public SettingsManager()
{
- _settingsPath = Path.Combine(Environment.CurrentDirectory, "settings.json");
- Settings = Load();
- }
-
+ _settingsPath = Path.Combine(Environment.CurrentDirectory, "settings.json");
+ Settings = Load();
+ }
+
private T Load()
{
if (!File.Exists(_settingsPath))