feat: Enhance Face AI upload functionality and UI
Some checks failed
Build Windows Avalonia / build (push) Failing after 1m48s
Release Windows Avalonia / build (push) Failing after 1m41s
Release Windows Avalonia / release (push) Has been skipped

- Updated MainWindow.axaml to increase height and add new UI elements for SSH upload configuration.
- Implemented commands for opening source and destination paths in file explorer.
- Added FaceUploadPath and SSH configuration properties to DataModel and AiSettingsViewModel.
- Introduced validation for FaceUploadPath format and commands for uploading face encoder output.
- Enhanced PickerPreferenceService to manage SSH credentials and upload preferences.
- Updated settings persistence to include FaceUploadPath and SSH preferences.
- Added tests for FaceUploadPath validation and upload command enabling logic.
This commit is contained in:
Maddo 2026-06-06 11:54:21 +02:00
commit e9142df97c
22 changed files with 1477 additions and 84 deletions

View file

@ -12,10 +12,9 @@ public sealed class LiteCatalogViewModel : ViewModelBase
private readonly ILogger<LiteCatalogViewModel> _logger;
private CatalogLiteConfiguration? _configuration;
private CancellationTokenSource? _processingTokenSource;
private string _configurationPath = string.Empty;
private string _sourcePath = string.Empty;
private string _destinationPath = string.Empty;
private string _processingStatus = "Carica una configurazione XML.";
private string _processingStatus = "Caricamento configurazione incorporata...";
private string _speedCounter = "-";
private int _processedImagesCount;
private int _totalImagesCount;
@ -36,36 +35,23 @@ public sealed class LiteCatalogViewModel : ViewModelBase
_imageProcessingCoordinator = imageProcessingCoordinator;
_logger = logger;
LoadConfigurationCommand = new AsyncCommand(RequestLoadConfigurationAsync, () => !IsProcessing);
SelectSourceFolderCommand = new AsyncCommand(RequestSourceFolderAsync, () => !IsProcessing);
SelectDestinationFolderCommand = new AsyncCommand(RequestDestinationFolderAsync, () => !IsProcessing);
StartProcessingCommand = new AsyncCommand(StartProcessingAsync, CanStartProcessing);
StopProcessingCommand = new AsyncCommand(StopProcessingAsync, () => IsProcessing);
}
public event EventHandler? LoadConfigurationRequested;
public event EventHandler? SelectSourceFolderRequested;
public event EventHandler? SelectDestinationFolderRequested;
public event EventHandler<LiteMessageEventArgs>? ShowMessageRequested;
public Action<Action>? UiInvoker { get; set; }
public AsyncCommand LoadConfigurationCommand { get; }
public AsyncCommand SelectSourceFolderCommand { get; }
public AsyncCommand SelectDestinationFolderCommand { get; }
public AsyncCommand StartProcessingCommand { get; }
public AsyncCommand StopProcessingCommand { get; }
public string ConfigurationPath
{
get => _configurationPath;
private set
{
_configurationPath = value;
NotifyPropertyChanged();
}
}
public string SourcePath
{
get => _sourcePath;
@ -77,6 +63,26 @@ public sealed class LiteCatalogViewModel : ViewModelBase
}
}
public string HorizontalText
{
get => _picSettings.TestoFirmaStart ?? string.Empty;
set
{
_picSettings.TestoFirmaStart = value ?? string.Empty;
NotifyPropertyChanged();
}
}
public string VerticalText
{
get => _picSettings.TestoFirmaStartV ?? string.Empty;
set
{
_picSettings.TestoFirmaStartV = value ?? string.Empty;
NotifyPropertyChanged();
}
}
public string DestinationPath
{
get => _destinationPath;
@ -159,25 +165,24 @@ public sealed class LiteCatalogViewModel : ViewModelBase
}
}
public async Task LoadConfigurationFromFileAsync(string filePath)
public void LoadEmbeddedConfiguration()
{
try
{
var configuration = await Task.Run(() => _configurationLoader.Load(filePath, _picSettings)).ConfigureAwait(false);
var configuration = _configurationLoader.LoadEmbedded(_picSettings);
RunOnUiThread(() =>
{
_configuration = configuration;
ConfigurationPath = configuration.FilePath;
SourcePath = configuration.SourcePath;
DestinationPath = configuration.DestinationPath;
ResetProgress("Configurazione caricata.");
});
_configuration = configuration;
SourcePath = configuration.SourcePath;
DestinationPath = configuration.DestinationPath;
NotifyPropertyChanged(nameof(HorizontalText));
NotifyPropertyChanged(nameof(VerticalText));
ResetProgress("Configurazione incorporata caricata.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante il caricamento della configurazione");
ShowMessage("Configurazione", $"Impossibile caricare la configurazione: {ex.GetBaseException().Message}");
_logger.LogError(ex, "Errore durante il caricamento della configurazione incorporata");
ProcessingStatus = "Errore caricamento configurazione incorporata.";
ShowMessage("Configurazione", $"Impossibile caricare la configurazione incorporata: {ex.GetBaseException().Message}");
}
}
@ -192,12 +197,6 @@ public sealed class LiteCatalogViewModel : ViewModelBase
return trimmed + Path.DirectorySeparatorChar;
}
private Task RequestLoadConfigurationAsync()
{
LoadConfigurationRequested?.Invoke(this, EventArgs.Empty);
return Task.CompletedTask;
}
private Task RequestSourceFolderAsync()
{
SelectSourceFolderRequested?.Invoke(this, EventArgs.Empty);
@ -222,7 +221,7 @@ public sealed class LiteCatalogViewModel : ViewModelBase
{
if (_configuration is null)
{
ShowMessage("Configurazione", "Carica prima una configurazione XML.");
ShowMessage("Configurazione", "La configurazione incorporata non e' disponibile.");
return;
}
@ -340,7 +339,6 @@ public sealed class LiteCatalogViewModel : ViewModelBase
private void RaiseCommandStates()
{
LoadConfigurationCommand.RaiseCanExecuteChanged();
SelectSourceFolderCommand.RaiseCanExecuteChanged();
SelectDestinationFolderCommand.RaiseCanExecuteChanged();
StartProcessingCommand.RaiseCanExecuteChanged();