From 4e1e63dcd3cdd2da7471b4b0e1acd42d92fdd54d Mon Sep 17 00:00:00 2001 From: Mateusz Kierepka Date: Fri, 27 Sep 2024 22:32:32 +0200 Subject: [PATCH] updated config window Added better model handling --- .idea/.idea.ChatAAC/.idea/avalonia.xml | 1 + ChatAAC/App.axaml | 15 +- ChatAAC/App.axaml.cs | 52 ++++- ChatAAC/Services/MacTtsService.cs | 4 +- ChatAAC/Services/OllamaClient.cs | 16 +- ChatAAC/Services/PictogramService.cs | 34 ++- ChatAAC/Services/TtsService.cs | 4 +- ChatAAC/ViewModels/ConfigViewModel.cs | 212 ++++++++++++++++++ ChatAAC/ViewModels/MainViewModel.cs | 73 +++--- ChatAAC/Views/ConfigWindow.axaml | 29 +++ ChatAAC/Views/ConfigWindow.axaml.cs | 13 ++ ChatAAC/Views/MainWindow.axaml | 2 + .../obj/Debug/net8.0/ChatAAC.AssemblyInfo.cs | 2 +- 13 files changed, 382 insertions(+), 75 deletions(-) create mode 100644 ChatAAC/ViewModels/ConfigViewModel.cs create mode 100644 ChatAAC/Views/ConfigWindow.axaml create mode 100644 ChatAAC/Views/ConfigWindow.axaml.cs diff --git a/.idea/.idea.ChatAAC/.idea/avalonia.xml b/.idea/.idea.ChatAAC/.idea/avalonia.xml index 9eda4d1..d6e3788 100644 --- a/.idea/.idea.ChatAAC/.idea/avalonia.xml +++ b/.idea/.idea.ChatAAC/.idea/avalonia.xml @@ -6,6 +6,7 @@ + diff --git a/ChatAAC/App.axaml b/ChatAAC/App.axaml index 5c7c7ab..4d61ebf 100644 --- a/ChatAAC/App.axaml +++ b/ChatAAC/App.axaml @@ -2,10 +2,19 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ChatAAC.App" xmlns:local="using:ChatAAC" - RequestedThemeVariant="Default"> - - + RequestedThemeVariant="Default" + Name="Chat AAC" + x:DataType="local:App"> + + + + + + + + + \ No newline at end of file diff --git a/ChatAAC/App.axaml.cs b/ChatAAC/App.axaml.cs index a86b1d3..91e0b5b 100644 --- a/ChatAAC/App.axaml.cs +++ b/ChatAAC/App.axaml.cs @@ -1,14 +1,15 @@ +using System; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using ChatAAC.Views; using ReactiveUI; using Avalonia.ReactiveUI; - +using ChatAAC.ViewModels; namespace ChatAAC; -public partial class App : Application +public class App : Application { public override void Initialize() { @@ -21,12 +22,51 @@ public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - // Line below is needed to remove Avalonia data validation. - // Without this line you will get duplicate validations from both Avalonia and CT - //BindingPlugins.DataValidators.RemoveAt(0); - desktop.MainWindow = new MainWindow(); + desktop.MainWindow = new MainWindow + { + DataContext = new MainViewModel() + }; } base.OnFrameworkInitializationCompleted(); } + + private void OpenConfigWindow() + { + var configWindow = new ConfigWindow() + { + DataContext = new ConfigViewModel() + }; + var mainWindow = (Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime) + ?.MainWindow; + if (mainWindow != null) + configWindow.ShowDialog(mainWindow); + else + configWindow.Show(); + + } + + private void OpenAboutWindow() + { + var aboutWindow = new AboutWindow + { + DataContext = new AboutViewModel() + }; + var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime) + ?.MainWindow; + if (mainWindow != null) + aboutWindow.ShowDialog(mainWindow); + else + aboutWindow.Show(); + } + + private void OnAboutClick(object? sender, EventArgs e) + { + OpenAboutWindow(); + } + + private void OnSettingsClick(object? sender, EventArgs e) + { + OpenConfigWindow(); + } } \ No newline at end of file diff --git a/ChatAAC/Services/MacTtsService.cs b/ChatAAC/Services/MacTtsService.cs index 647cee8..325e720 100644 --- a/ChatAAC/Services/MacTtsService.cs +++ b/ChatAAC/Services/MacTtsService.cs @@ -12,7 +12,7 @@ public async Task SpeakAsync(string text) if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Tekst do odczytania nie może być pusty.", nameof(text)); - if (!IsMacOS()) + if (!IsMacOs()) throw new PlatformNotSupportedException("TTS za pomocą 'say' jest wspierane tylko na macOS."); var processStartInfo = new ProcessStartInfo @@ -51,7 +51,7 @@ public async Task SpeakAsync(string text) } } - private bool IsMacOS() + private bool IsMacOs() { return RuntimeInformation.IsOSPlatform(OSPlatform.OSX); } diff --git a/ChatAAC/Services/OllamaClient.cs b/ChatAAC/Services/OllamaClient.cs index a396e4b..175e581 100644 --- a/ChatAAC/Services/OllamaClient.cs +++ b/ChatAAC/Services/OllamaClient.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using ChatAAC.ViewModels; using OllamaSharp; namespace ChatAAC.Services; @@ -12,19 +13,10 @@ public class OllamaClient public OllamaClient(string apiUrl) { - if (string.IsNullOrWhiteSpace(apiUrl)) - apiUrl = "http://localhost:11434"; - - if (!apiUrl.StartsWith("http")) - apiUrl = "http://" + apiUrl; - - if (apiUrl.IndexOf(':', 5) < 0) - apiUrl += ":11434"; - - - Console.WriteLine($"Connecting to {apiUrl} ..."); + + Console.WriteLine($"Connecting to {ConfigViewModel.Instance.OllamaAddress} ..."); - var ollama = new OllamaApiClient(apiUrl) + var ollama = new OllamaApiClient(ConfigViewModel.Instance.OllamaAddress) { SelectedModel = "gemma2" }; diff --git a/ChatAAC/Services/PictogramService.cs b/ChatAAC/Services/PictogramService.cs index 3676bd2..cd5c185 100644 --- a/ChatAAC/Services/PictogramService.cs +++ b/ChatAAC/Services/PictogramService.cs @@ -69,7 +69,7 @@ public PictogramService() // Pobierz dane z API ARASAAC try { - var url = "https://api.arasaac.org/v1/pictograms/all/pl"; + const string url = "https://api.arasaac.org/v1/pictograms/all/pl"; var response = await HttpClient.GetAsync(url).ConfigureAwait(false); if (response.IsSuccessStatusCode) @@ -83,21 +83,14 @@ public PictogramService() var pictograms = JsonSerializer.Deserialize>(responseData); // Sprawdź, czy deserializacja zwróciła dane - if (pictograms is { Count: > 0 }) - { - await CheckAndDownloadMissingImages(pictograms); - return pictograms; - } - else - { - throw new Exception("Pobrano puste dane z API ARASAAC."); - } - } - else - { - throw new Exception( - $"Niepowodzenie pobierania danych z ARASAAC API. Status Code: {response.StatusCode}"); + if (pictograms is not { Count: > 0 }) throw new Exception("Pobrano puste dane z API ARASAAC."); + await CheckAndDownloadMissingImages(pictograms); + + return pictograms; } + + throw new Exception( + $"Niepowodzenie pobierania danych z ARASAAC API. Status Code: {response.StatusCode}"); } catch (Exception ex) { @@ -109,13 +102,12 @@ public PictogramService() private async Task CheckAndDownloadMissingImages(List pictograms) { - foreach (var pictogram in pictograms) + foreach (var pictogram in from pictogram in pictograms + let imagePath = Path.Combine(_cacheDirectory, $"{pictogram.Id}.png") + where !File.Exists(imagePath) + select pictogram) { - var imagePath = Path.Combine(_cacheDirectory, $"{pictogram.Id}.png"); - if (!File.Exists(imagePath)) - { - await DownloadPictogramImageAsync(pictogram.Id.ToString()); - } + await DownloadPictogramImageAsync(pictogram.Id.ToString()); } } diff --git a/ChatAAC/Services/TtsService.cs b/ChatAAC/Services/TtsService.cs index d4e66a1..0f4e1e2 100644 --- a/ChatAAC/Services/TtsService.cs +++ b/ChatAAC/Services/TtsService.cs @@ -17,7 +17,7 @@ public async Task SpeakAsync(string text) if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Tekst do odczytania nie może być pusty.", nameof(text)); - if (!IsMacOS()) + if (!IsMacOs()) throw new PlatformNotSupportedException("TTS za pomocą 'say' jest wspierane tylko na macOS."); // Przygotowanie procesu @@ -62,7 +62,7 @@ public async Task SpeakAsync(string text) /// Sprawdza, czy aplikacja działa na macOS. /// /// Prawda, jeśli system operacyjny to macOS; w przeciwnym razie fałsz. - private bool IsMacOS() + private bool IsMacOs() { return RuntimeInformation.IsOSPlatform(OSPlatform.OSX); } diff --git a/ChatAAC/ViewModels/ConfigViewModel.cs b/ChatAAC/ViewModels/ConfigViewModel.cs new file mode 100644 index 0000000..5112a85 --- /dev/null +++ b/ChatAAC/ViewModels/ConfigViewModel.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Text.Json; +using System.Text.Json.Serialization; +using OllamaSharp; +using ReactiveUI; +using System.Threading.Tasks; + +namespace ChatAAC.ViewModels; + +public class ConfigViewModel : ReactiveObject +{ + private static ConfigViewModel? _instance; + private static readonly object Lock = new object(); + + private string _ollamaAddress = "http://localhost:11434"; + private string? _selectedModel; + private bool _showSex; + private bool _showViolence; + private bool _showAac; + private bool _showSchematic; + private string? _selectedLanguage; + private int _loadedIconsCount; + + [JsonIgnore] + public ObservableCollection Models { get; } = new ObservableCollection(); + [JsonIgnore] + public ObservableCollection Languages { get; } = new ObservableCollection(); + [JsonIgnore] + public ReactiveCommand SaveCommand { get; } + + private const string ConfigFilePath = "config.json"; + + // Properties + public string OllamaAddress + { + get => _ollamaAddress; + set => this.RaiseAndSetIfChanged(ref _ollamaAddress, value); + } + + public string? SelectedModel + { + get => _selectedModel; + set => this.RaiseAndSetIfChanged(ref _selectedModel, value); + } + + public bool ShowSex + { + get => _showSex; + set => this.RaiseAndSetIfChanged(ref _showSex, value); + } + + public bool ShowViolence + { + get => _showViolence; + set => this.RaiseAndSetIfChanged(ref _showViolence, value); + } + + public bool ShowAac + { + get => _showAac; + set => this.RaiseAndSetIfChanged(ref _showAac, value); + } + + public bool ShowSchematic + { + get => _showSchematic; + set => this.RaiseAndSetIfChanged(ref _showSchematic, value); + } + + public string? SelectedLanguage + { + get => _selectedLanguage; + set => this.RaiseAndSetIfChanged(ref _selectedLanguage, value); + } + + public int LoadedIconsCount + { + get => _loadedIconsCount; + set => this.RaiseAndSetIfChanged(ref _loadedIconsCount, value); + } + + public static ConfigViewModel Instance + { + get + { + if (_instance == null) + { + lock (Lock) + { + if (_instance == null) + { + _instance = new ConfigViewModel(); + } + } + } + return _instance; + } + } + + public ConfigViewModel() + { + LoadConfiguration(); + NormalizeOllamaAddress(); + SaveCommand = ReactiveCommand.Create(SaveConfiguration); + InitializeData(); + } + + private void NormalizeOllamaAddress() + { + if (!OllamaAddress.StartsWith("http")) + OllamaAddress = "http://" + OllamaAddress; + + if (OllamaAddress.IndexOf(':', 5) < 0) + OllamaAddress += ":11434"; + } + + private void InitializeData() + { + Languages.Add("Polski"); + Languages.Add("English"); + + Task.Run(InitializeModelsAsync); + } + + private async Task InitializeModelsAsync() + { + try + { + var ollamaClient = new OllamaApiClient(OllamaAddress); + var models = await ollamaClient.ListLocalModels(); + var sortedModels = models.OrderBy(m => m.Name); + + foreach (var model in sortedModels) + { + Models.Add(model.Name); + } + } + catch (Exception ex) + { + // Handle or log the exception + Console.WriteLine($"Error initializing models: {ex.Message}"); + } + } + + private void LoadConfiguration() + { + if (File.Exists(ConfigFilePath)) + { + var json = File.ReadAllText(ConfigFilePath); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + var config = JsonSerializer.Deserialize(json, options); + if (config != null) + { + OllamaAddress = config.OllamaAddress; + SelectedModel = config.SelectedModel; + ShowSex = config.ShowSex; + ShowViolence = config.ShowViolence; + ShowAac = config.ShowAac; + ShowSchematic = config.ShowSchematic; + SelectedLanguage = config.SelectedLanguage; + LoadedIconsCount = config.LoadedIconsCount; + } + } + } + + private void SaveConfiguration() + { + var configData = new ConfigData + { + OllamaAddress = OllamaAddress, + SelectedModel = SelectedModel, + ShowSex = ShowSex, + ShowViolence = ShowViolence, + ShowAac = ShowAac, + ShowSchematic = ShowSchematic, + SelectedLanguage = SelectedLanguage, + LoadedIconsCount = LoadedIconsCount + }; + + var options = new JsonSerializerOptions + { + WriteIndented = true + }; + var json = JsonSerializer.Serialize(configData, options); + File.WriteAllText(ConfigFilePath, json); + } + + public void UpdateLoadedIconsCount(int count) + { + LoadedIconsCount = count; + SaveConfiguration(); + } +} + +public class ConfigData +{ + public string OllamaAddress { get; set; } = ""; + public string? SelectedModel { get; set; } + public bool ShowSex { get; set; } + public bool ShowViolence { get; set; } + public bool ShowAac { get; set; } + public bool ShowSchematic { get; set; } + public string? SelectedLanguage { get; set; } + public int LoadedIconsCount { get; set; } +} \ No newline at end of file diff --git a/ChatAAC/ViewModels/MainViewModel.cs b/ChatAAC/ViewModels/MainViewModel.cs index 2a018b3..d2f08af 100644 --- a/ChatAAC/ViewModels/MainViewModel.cs +++ b/ChatAAC/ViewModels/MainViewModel.cs @@ -27,6 +27,7 @@ public class MainViewModel : ViewModelBase public ObservableCollection Categories { get; set; } = []; public ObservableCollection Tags { get; set; } = []; + public ReactiveCommand ExitCommand { get; } private readonly PictogramService _pictogramService; private readonly OllamaClient _ollamaClient; // Klient OllamaSharp @@ -48,6 +49,13 @@ public string SearchQuery } } + + private void ExitApplication() + { + // Logic to exit the application + Environment.Exit(0); + } + private string _tagSearch = string.Empty; public string TagSearch @@ -83,12 +91,10 @@ public string ConstructedSentence public ReactiveCommand PictogramClickedCommand { get; } public ReactiveCommand RemovePictogramCommand { get; } public ReactiveCommand SpeakCommand { get; } - public ReactiveCommand SendToAiCommand { get; } public ReactiveCommand SpeakAiResponseCommand { get; } public ReactiveCommand ToggleFullScreenCommand { get; } public ReactiveCommand CopySentenceCommand { get; } - public ReactiveCommand OpenAboutCommand { get; } public ReactiveCommand CopyHistoryItemCommand { get; } public ReactiveCommand ClearHistoryCommand { get; } public ReactiveCommand CopyAiResponseCommand { get; } @@ -127,6 +133,8 @@ public MainViewModel() _pictogramService = new PictogramService(); _ollamaClient = new OllamaClient("http://localhost:11434"); // Upewnij się, że adres jest poprawny + ExitCommand = ReactiveCommand.Create(ExitApplication); + // Inicjalizacja TTS w zależności od platformy if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { @@ -145,7 +153,7 @@ public MainViewModel() throw new PlatformNotSupportedException("Platforma nie jest wspierana przez TTS."); } - OpenAboutCommand = ReactiveCommand.Create(OpenAboutWindow); + PictogramClickedCommand = ReactiveCommand.CreateFromTask(OnPictogramClickedAsync); RemovePictogramCommand = ReactiveCommand.Create(OnRemovePictogram); SpeakCommand = ReactiveCommand.Create(OnSpeak); @@ -260,22 +268,43 @@ private void FilterPictograms() var filtered = _allPictograms?.AsEnumerable() ?? []; + // Filtrowanie na podstawie konfiguracji + var config = ConfigViewModel.Instance; + + if (!config.ShowSex) + filtered = filtered.Where(p => !p.Sex); + + if (!config.ShowViolence) + filtered = filtered.Where(p => !p.Violence); + + if (config.ShowAac) + filtered = filtered.Where(p => p.Aac); + + if (config.ShowSchematic) + filtered = filtered.Where(p => p.Schematic); + + // Dodatkowe filtrowanie dla AacColor, Skin i Hair + // Zakładam, że te pola powinny być zawsze pokazywane, chyba że zostaną dodane do konfiguracji + // filtered = filtered.Where(p => p.AacColor); + // filtered = filtered.Where(p => p.Skin); + // filtered = filtered.Where(p => p.Hair); + // Filtrowanie po wybranej kategorii if (SelectedCategory != null && !string.IsNullOrWhiteSpace(SelectedCategory.Id)) { - if (SelectedCategory.Id == "core") + switch (SelectedCategory.Id) { - filtered = filtered.Where(p => - p.Categories.Any(c => c.IndexOf("core", StringComparison.OrdinalIgnoreCase) >= 0)); - } - else if (SelectedCategory.Id == string.Empty) - { - // Pokazuj wszystkie piktogramy - } - else - { - filtered = filtered.Where(p => - p.Categories.Any(c => c.Equals(SelectedCategory.Name, StringComparison.OrdinalIgnoreCase))); + case "core": + filtered = filtered.Where(p => + p.Categories.Any(c => c.IndexOf("core", StringComparison.OrdinalIgnoreCase) >= 0)); + break; + case "": + // Pokazuj wszystkie piktogramy + break; + default: + filtered = filtered.Where(p => + p.Categories.Any(c => c.Equals(SelectedCategory.Name, StringComparison.OrdinalIgnoreCase))); + break; } } @@ -291,7 +320,6 @@ private void FilterPictograms() current.Where(p => p.Tags.Any(pt => pt.Contains(tag, StringComparison.OrdinalIgnoreCase)))); } - // Filtrowanie po zapytaniu wyszukiwania if (!string.IsNullOrWhiteSpace(SearchQuery)) { @@ -299,7 +327,7 @@ private void FilterPictograms() p.Keywords.Any(k => k.KeywordKeyword.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase))); } - // **Nowy filtr: Usunięcie piktogramów z pustym polem Text** + // Usunięcie piktogramów z pustym polem Text filtered = filtered.Where(p => !string.IsNullOrWhiteSpace(p.Text)); // Sortowanie po KeywordKeyword @@ -531,17 +559,6 @@ private void OnCopySentence() } } - private void OpenAboutWindow() - { - var aboutWindow = new AboutWindow - { - DataContext = new AboutViewModel() - }; - var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime) - ?.MainWindow; - if (mainWindow != null) - aboutWindow.ShowDialog(mainWindow); - } private void CopyToClipboard(string textToClipboard) { diff --git a/ChatAAC/Views/ConfigWindow.axaml b/ChatAAC/Views/ConfigWindow.axaml new file mode 100644 index 0000000..b3c8cf9 --- /dev/null +++ b/ChatAAC/Views/ConfigWindow.axaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + +