feat: Add support for thumbnail inclusion in AI processing and enhance UI bindings
Some checks failed
Build Windows Avalonia / build (push) Failing after 1m48s
Build Windows Avalonia / release (push) Has been skipped

This commit is contained in:
MaddoScientisto 2026-05-09 17:53:15 +02:00
commit 7e105e3738
9 changed files with 235 additions and 27 deletions

View file

@ -16,6 +16,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using AIFotoONLUS.Core;
using AutoMapper;
using MaddoShared;
using Microsoft.Extensions.Logging;
@ -117,6 +118,7 @@ namespace ImageCatalog_2
// Load available fonts
AvailableFonts = LoadAvailableFonts();
RefreshNumberAiGpuCapabilities();
RefreshFaceExecutableCapabilities();
}
@ -131,6 +133,16 @@ namespace ImageCatalog_2
{
// user cancelled
}
catch (Exception ex)
{
_logger.LogError(ex, "AI extraction failed");
if (UseNumberAiGpu)
{
RefreshNumberAiGpuCapabilities();
}
await ShowErrorMessageAsync("Errore AI", ex.GetBaseException().Message).ConfigureAwait(false);
}
finally
{
MainToken = null;
@ -162,12 +174,19 @@ namespace ImageCatalog_2
{
SearchRoot = searchRoot,
Recursive = recursive,
IncludeThumbnails = IncludeNumberAiThumbnails,
ModelsFolderPath = ModelsFolderPath,
UseGpu = UseNumberAiGpu,
CsvOutputPath = CsvOutputPath
},
token,
result => InvokeOnUiThreadAsync(() => PreviewResults.Add(result)),
result => InvokeOnUiThreadAsync(() =>
{
if (!string.IsNullOrWhiteSpace(result.Text))
{
PreviewResults.Add(result);
}
}),
progress => InvokeOnUiThreadAsync(() => AiProgress = progress)).ConfigureAwait(false);
}
@ -200,7 +219,11 @@ namespace ImageCatalog_2
public string ModelsFolderPath
{
get => _ai.ModelsFolderPath;
set => _ai.ModelsFolderPath = value;
set
{
_ai.ModelsFolderPath = value;
RefreshNumberAiGpuCapabilities();
}
}
public string CsvOutputPath
@ -212,7 +235,19 @@ namespace ImageCatalog_2
public bool UseNumberAiGpu
{
get => _ai.UseNumberAiGpu;
set => _ai.UseNumberAiGpu = value;
set => SetUseNumberAiGpu(value);
}
public bool NumberAiGpuOptionEnabled
{
get => _ai.NumberAiGpuOptionEnabled;
private set => _ai.NumberAiGpuOptionEnabled = value;
}
public bool IncludeNumberAiThumbnails
{
get => _ai.IncludeNumberAiThumbnails;
set => _ai.IncludeNumberAiThumbnails = value;
}
public string FaceExecutablePath
@ -1302,23 +1337,6 @@ namespace ImageCatalog_2
},
speed => { SpeedCounter = speed; }).ConfigureAwait(false);
// AI integration: OCR runs over processed output so it matches the face AI input folder.
if (ExtractNumbers)
{
try
{
await RunAiExtractionCoreAsync(token, useDestination: true, recursive: true);
}
catch (OperationCanceledException)
{
_logger.LogInformation("AI extraction canceled");
}
catch (Exception ex)
{
_logger.LogError(ex, "AI extraction failed");
}
}
SpeedCounter = runResult.FinalSpeedCounter;
}
catch (OperationCanceledException)
@ -1762,6 +1780,71 @@ namespace ImageCatalog_2
}
}
private void RefreshNumberAiGpuCapabilities()
{
if (!TryBuildNumberAiModelConfiguration(out var configuration))
{
NumberAiGpuOptionEnabled = false;
_ai.UseNumberAiGpu = false;
return;
}
NumberAiGpuOptionEnabled = NumberRecognitionEngine.TryValidateGpuRuntime(configuration, _logger, out _);
if (!NumberAiGpuOptionEnabled)
{
_ai.UseNumberAiGpu = false;
}
}
private void SetUseNumberAiGpu(bool value)
{
if (!NumberAiGpuOptionEnabled)
{
_ai.UseNumberAiGpu = false;
return;
}
_ai.UseNumberAiGpu = value;
}
private bool TryBuildNumberAiModelConfiguration(out ModelConfiguration configuration)
{
configuration = null!;
if (string.IsNullOrWhiteSpace(ModelsFolderPath))
{
return false;
}
var modelsRoot = Path.GetFullPath(ModelsFolderPath.Trim().Trim('"'));
if (!Directory.Exists(modelsRoot))
{
return false;
}
configuration = new ModelConfiguration
{
DetectionCfg = Path.Combine(modelsRoot, "detection.cfg"),
DetectionWeights = Path.Combine(modelsRoot, "detection.weights"),
RecognitionCfg = Path.Combine(modelsRoot, "recognition.cfg"),
RecognitionWeights = Path.Combine(modelsRoot, "recognition.weights"),
UseGpu = true
};
return File.Exists(configuration.DetectionCfg)
&& File.Exists(configuration.DetectionWeights)
&& File.Exists(configuration.RecognitionCfg)
&& File.Exists(configuration.RecognitionWeights);
}
private Task ShowErrorMessageAsync(string title, string message)
{
return InvokeOnUiThreadAsync(() =>
{
ShowMessageRequested?.Invoke(this, Tuple.Create(title, message, 0));
});
}
private void SetUseFaceGpu(bool value)
{
var currentValue = _ai.UseFaceGpu;