using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Media.Imaging; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using finder2e_foundry_converter.Models; using finder2e_foundry_converter.Services; namespace finder2e_foundry_converter.ViewModels { public partial class MainWindowViewModel : ViewModelBase { private readonly LmStudioService _lmStudioService = new(); private readonly OllamaService _ollamaService = new(); private ILlmService CurrentService => SelectedProvider == "Ollama" ? (ILlmService)_ollamaService : _lmStudioService; [ObservableProperty] [NotifyPropertyChangedFor(nameof(CurrentAddress))] private string _lmStudioAddress = "http://localhost:1234"; [ObservableProperty] [NotifyPropertyChangedFor(nameof(CurrentAddress))] private string _ollamaAddress = "http://localhost:11434"; public string CurrentAddress { get => SelectedProvider == "Ollama" ? OllamaAddress : LmStudioAddress; set { if (SelectedProvider == "Ollama") OllamaAddress = value; else LmStudioAddress = value; OnPropertyChanged(nameof(CurrentAddress)); } } [ObservableProperty] private bool _isAddressVisible = false; [ObservableProperty] [NotifyPropertyChangedFor(nameof(CurrentAddress))] private string _selectedProvider = "LM Studio"; public ObservableCollection Providers { get; } = new() { "LM Studio", "Ollama" }; [ObservableProperty] private string? _selectedModel; public ObservableCollection Models { get; } = new(); [ObservableProperty] private string? _selectedCategory; public ObservableCollection Categories { get; } = new() { "NPCs" }; [ObservableProperty] private string _name = string.Empty; [ObservableProperty] private string _description = string.Empty; public ObservableCollection ImageItems { get; } = new(); [ObservableProperty] private string _statusMessage = string.Empty; [ObservableProperty] private bool _isErrorVisible = false; [ObservableProperty] private string _errorText = string.Empty; public MainWindowViewModel() { // Load saved preferences would go here if we had a settings service _ = StartModelRefreshLoop(); } partial void OnSelectedProviderChanged(string value) { Models.Clear(); SelectedModel = null; _ = RefreshModels(); } [RelayCommand] private void ToggleAddress() { IsAddressVisible = !IsAddressVisible; } [RelayCommand] private async Task RefreshModels() { var models = await CurrentService.GetModelsAsync(CurrentAddress); Models.Clear(); foreach (var m in models) { Models.Add(m); } if (models.Count > 0 && !models[0].StartsWith("Error")) { IsErrorVisible = false; if (SelectedModel == null || !Models.Contains(SelectedModel)) { SelectedModel = Models.FirstOrDefault(); } } else { ErrorText = models.FirstOrDefault() ?? $"Error connecting to {SelectedProvider}"; IsErrorVisible = true; IsAddressVisible = true; // Auto-show on failure } } [RelayCommand] private async Task Generate() { if (string.IsNullOrEmpty(SelectedModel)) { StatusMessage = "Please select a model first."; return; } StatusMessage = "Generating..."; var result = await CurrentService.GenerateResponseAsync(CurrentAddress, SelectedModel, Description, ImageItems.Select(i => i.Path).ToList()); StatusMessage = result; } [RelayCommand] private void AddImagePath(string path) { if (!string.IsNullOrWhiteSpace(path) && File.Exists(path)) { if (ImageItems.Any(i => i.Path == path)) return; try { // Load thumbnail using var stream = File.OpenRead(path); var bitmap = new Bitmap(stream); // We could resize it here for efficiency if needed, but for now let's just use it ImageItems.Add(new ImageItem { Path = path, Thumbnail = bitmap }); } catch (Exception ex) { StatusMessage = $"Error loading image: {ex.Message}"; } } } [RelayCommand] private void RemoveImage(ImageItem item) { ImageItems.Remove(item); } private async Task StartModelRefreshLoop() { while (true) { await RefreshModels(); await Task.Delay(IsErrorVisible ? 3000 : 10000); } } } }