CtrEditor/ViewModels/MatrixPreviewViewModel.cs

648 lines
24 KiB
C#
Raw Normal View History

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CtrEditor.ObjetosSim;
using CtrEditor.ObjetosSim.Extraccion_Datos;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Threading; // Add this line
using System.Linq;
using ClosedXML.Excel;
using System.Diagnostics;
namespace CtrEditor.PopUps
{
public class MatrixItem : ObservableObject
{
public string TagName { get; set; }
private string columnName;
public string ColumnName
{
get => columnName;
set => SetProperty(ref columnName, value);
}
private int columnNumber;
public int ColumnNumber
{
get => columnNumber;
set => SetProperty(ref columnNumber, value);
}
public string Value { get; set; }
public string Type { get; set; }
public bool IsCloned { get; set; }
public UniqueId Id { get; set; }
public string Source_Image { get; set; }
public int Copy_Number { get; set; } // Add this property
}
public partial class MatrixPreviewViewModel : ObservableObject
{
private Window _window;
private MainViewModel _mainViewModel;
private DataGrid _dataGrid;
private readonly Services.LLMService _llmService;
public MatrixPreviewViewModel()
{
_llmService = new Services.LLMService();
}
[ObservableProperty]
private ObservableCollection<MatrixItem> matrixItems;
[ObservableProperty]
private ObservableCollection<Dictionary<string, string>> matrixRows;
[ObservableProperty]
private GridLength detailsHeight = new GridLength(150);
[ObservableProperty]
private double detailsHeightValue = 150;
private ObservableCollection<string> selectedImages;
partial void OnDetailsHeightValueChanged(double value)
{
DetailsHeight = new GridLength(value);
}
public void Initialize(MainViewModel mainViewModel, Window window, ObservableCollection<string> images = null)
{
_mainViewModel = mainViewModel;
_window = window;
_dataGrid = (_window as MatrixPreviewWindow)?.MatrixPreview;
selectedImages = images ?? new ObservableCollection<string> { mainViewModel.SelectedImage };
MatrixItems = new ObservableCollection<MatrixItem>();
MatrixRows = new ObservableCollection<Dictionary<string, string>>();
AnalyzeMatrixMultiPage();
}
private void UpdateMatrixPreview()
{
if (_dataGrid == null) return;
if (MatrixRows == null) MatrixRows = new ObservableCollection<Dictionary<string, string>>();
MatrixRows.Clear();
_dataGrid.Columns.Clear();
if (!MatrixItems.Any()) return;
// Group items by source image and then by column number
var itemsByImage = MatrixItems.GroupBy(x => x.Source_Image);
// Ordenar items por número de columna
var orderedItems = MatrixItems
.GroupBy(x => x.ColumnNumber)
.OrderBy(g => g.Key)
.Select(g => g.First())
.ToList();
// Crear columnas
foreach (var item in orderedItems)
{
var column = new DataGridTextColumn
{
Header = $"{item.ColumnNumber}:{item.ColumnName}",
Binding = new Binding($"[{item.ColumnNumber}]"),
Width = DataGridLength.Auto
};
_dataGrid.Columns.Add(column);
}
// Process each image's data
foreach (var imageGroup in itemsByImage)
{
// Calculate max rows needed for this image
int maxRows = 1;
var clonedItems = imageGroup.Where(x => x.IsCloned).ToList();
if (clonedItems.Any())
{
maxRows = clonedItems.Max(x => x.Copy_Number) + 1;
}
// Create rows for this image
for (int row = 0; row < maxRows; row++)
{
var rowData = new Dictionary<string, string>();
// Add image identifier
rowData["Image"] = imageGroup.Key;
// Add fixed (non-cloned) values in all rows
foreach (var item in imageGroup.Where(x => !x.IsCloned))
{
if (item.ColumnNumber > 0)
{
rowData[item.ColumnNumber.ToString()] = item.Value ?? string.Empty;
}
}
// Add cloned values only in their corresponding row
foreach (var item in imageGroup.Where(x => x.IsCloned))
{
if (item.ColumnNumber > 0 && item.Copy_Number == row)
{
rowData[item.ColumnNumber.ToString()] = item.Value ?? string.Empty;
}
}
MatrixRows.Add(rowData);
}
}
}
private void UpdateColumnHeaders()
{
if (_dataGrid == null) return;
foreach (var col in _dataGrid.Columns.Cast<DataGridColumn>())
{
if (col is DataGridTextColumn textCol)
{
var item = MatrixItems.FirstOrDefault(x => x.ColumnName == (textCol.Header?.ToString() ?? "").Split(':').Last());
if (item != null)
{
textCol.Header = $"{item.ColumnNumber}:{item.ColumnName}";
}
}
}
}
private async void AnalyzeMatrixMultiPage()
{
MatrixItems.Clear();
bool originalHasUnsavedChanges = _mainViewModel.HasUnsavedChanges;
_mainViewModel.HasUnsavedChanges = false;
try
{
foreach (var imageName in selectedImages)
{
// Store current image
var currentImage = _mainViewModel.SelectedImage;
// Change to target image and wait for UI update
_mainViewModel.SelectedImage = imageName;
await _mainViewModel.WaitForUIUpdateAsync();
// Analyze current page
var items = AnalyzePage();
foreach (var item in items)
{
item.Source_Image = imageName;
MatrixItems.Add(item);
}
// Restore original image if needed
if (currentImage != imageName)
{
_mainViewModel.SelectedImage = currentImage;
await _mainViewModel.WaitForUIUpdateAsync();
}
}
}
finally
{
_mainViewModel.HasUnsavedChanges = originalHasUnsavedChanges;
}
ResolveColumnConflicts();
UpdateMatrixPreview();
}
/// <summary>
/// Analyzes the current page and collects all extraction tags data for matrix preview.
/// </summary>
/// <remarks>
/// The method processes three types of extraction tags:
/// 1. Fixed Tags: Tags without Search Templates association
/// 2. Grouped Tags: Tags linked to Search Templates but not cloned
/// 3. Cloned Tags: Tags created by Search Templates pattern matching
///
/// Workflow:
/// 1. Filters objects by visibility on current page
/// 2. Groups tags by their type (fixed, grouped, cloned)
/// 3. Performs OCR extraction on each tag
/// 4. Creates MatrixItems with extracted data
///
/// Data Organization:
/// - Fixed tags maintain their original position and values
/// - Grouped tags are associated with their Search Templates
/// - Cloned tags inherit Copy_Number from their creation order in pattern matching
///
/// Related Classes:
/// - osExtraccionTag: Contains OCR extraction logic and tag properties
/// - osBuscarCoincidencias: Handles pattern matching and tag cloning
/// </remarks>
/// <returns>A list of MatrixItems containing all processed tag data for the current page</returns>
private List<MatrixItem> AnalyzePage()
{
var items = new List<MatrixItem>();
// Filter visible objects by type
var osBuscarCoincidencias_List = _mainViewModel.ObjetosSimulables
.OfType<osBuscarCoincidencias>()
.Where(tag => tag.Show_On_This_Page)
.ToList();
// Get base tags that are linked to Search Templates
var osExtraccionTagBaseGrouped_List = _mainViewModel.ObjetosSimulables
.OfType<osExtraccionTag>()
.Where(tag => tag.Show_On_This_Page && !tag.Cloned && tag.Id_Search_Templates != null && tag.Id_Search_Templates != "")
.ToList();
// Get fixed tags (not linked to Search Templates)
var osExtraccionTagBaseFix_List = _mainViewModel.ObjetosSimulables
.OfType<osExtraccionTag>()
.Where(tag => tag.Show_On_This_Page && !tag.Cloned && (tag.Id_Search_Templates == null || tag.Id_Search_Templates == ""))
.ToList();
// Get cloned tags (created by Search Templates)
var osExtraccionTagCloned_List = _mainViewModel.ObjetosSimulables
.OfType<osExtraccionTag>()
.Where(tag => tag.Show_On_This_Page && tag.Cloned)
.ToList();
// Process fixed tags
foreach (var tag in osExtraccionTagBaseFix_List)
{
tag.CaptureImageAreaAndDoOCR();
items.Add(new MatrixItem
{
TagName = tag.Nombre,
ColumnName = tag.Collumn_name,
ColumnNumber = tag.Collumn_number,
Value = tag.Tag_extract,
Type = "Fixed",
IsCloned = false,
Id = tag.Id
});
}
// Process grouped tags
foreach (var tag in osExtraccionTagBaseGrouped_List)
{
tag.CaptureImageAreaAndDoOCR();
items.Add(new MatrixItem
{
TagName = tag.Nombre,
ColumnName = tag.Collumn_name,
ColumnNumber = tag.Collumn_number,
Value = tag.Tag_extract,
Type = $"Grouped ({tag.Id_Search_Templates})",
IsCloned = false,
Id = tag.Id
});
}
// Process cloned tags
foreach (var tag in osExtraccionTagCloned_List)
{
tag.CaptureImageAreaAndDoOCR();
items.Add(new MatrixItem
{
TagName = tag.Nombre,
ColumnName = tag.Collumn_name,
ColumnNumber = tag.Collumn_number,
Value = tag.Tag_extract,
Type = "Cloned",
IsCloned = true,
Id = tag.Id,
Copy_Number = tag.Copy_Number
});
}
return items;
}
partial void OnMatrixItemsChanged(ObservableCollection<MatrixItem> value)
{
if (value != null)
{
foreach (var item in value)
{
item.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(MatrixItem.ColumnNumber) ||
e.PropertyName == nameof(MatrixItem.ColumnName))
{
UpdateColumnHeaders();
}
};
}
}
UpdateMatrixPreview();
}
[RelayCommand]
private async void ApplyChanges()
{
// Resolver solo los conflictos reales, mantener el orden existente
var conflicts = MatrixItems
.GroupBy(x => x.ColumnNumber)
.Where(g => g.Select(x => x.ColumnName).Distinct().Count() > 1)
.Any();
if (conflicts)
{
ResolveColumnConflicts();
}
// Validar números de columna
var columnNumbers = MatrixItems.Select(x => x.ColumnNumber).OrderBy(x => x).ToList();
for (int i = 0; i < columnNumbers.Count; i++)
{
if (columnNumbers[i] <= 0)
{
MessageBox.Show("Column numbers must be greater than 0", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
}
bool originalHasUnsavedChanges = _mainViewModel.HasUnsavedChanges;
_mainViewModel.HasUnsavedChanges = false;
try
{
foreach (var imageName in selectedImages)
{
// Store current image
var currentImage = _mainViewModel.SelectedImage;
// Change to target image and wait for UI update
_mainViewModel.SelectedImage = imageName;
await Task.Yield();
Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
// Apply changes for current page
var pageItems = MatrixItems.Where(x => x.Source_Image == imageName);
foreach (var item in pageItems)
{
// Find the tag by Id instead of direct reference
var tag = _mainViewModel.ObjetosSimulables
.OfType<osExtraccionTag>()
.FirstOrDefault(t => t.Id == item.Id);
if (tag != null)
{
tag.Collumn_name = item.ColumnName;
tag.Collumn_number = item.ColumnNumber;
}
}
// Restore original image if needed
if (currentImage != imageName)
{
_mainViewModel.SelectedImage = currentImage;
await Task.Yield();
Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
}
_mainViewModel.SaveStateObjetosSimulables();
}
}
finally
{
_mainViewModel.HasUnsavedChanges = originalHasUnsavedChanges;
}
_window.Close();
}
[RelayCommand]
private void Close()
{
_window.Close();
}
[RelayCommand]
private void RegenerateMatrix()
{
ResolveColumnConflicts();
UpdateMatrixPreview();
}
private void ResolveColumnConflicts()
{
// Paso 1: Mantener el orden actual y resolver solo los conflictos reales
var columnGroups = MatrixItems
.GroupBy(x => x.ColumnNumber)
.Where(g => g.Select(x => x.ColumnName).Distinct().Count() > 1)
.ToList();
foreach (var group in columnGroups)
{
var nameGroups = group
.GroupBy(x => x.ColumnName)
.OrderByDescending(g => g.Count())
.ToList();
// El grupo más grande mantiene su número
var largestGroup = nameGroups.First();
// Solo reasignar números para grupos que tengan conflicto
for (int i = 1; i < nameGroups.Count; i++)
{
var newColumnNumber = GetNextAvailableColumnNumber();
foreach (var item in nameGroups[i])
{
item.ColumnNumber = newColumnNumber;
}
}
}
// Paso 2: Asegurarse de que los números son consecutivos sin alterar el orden relativo
var orderedItems = MatrixItems
.OrderBy(x => x.ColumnNumber)
.GroupBy(x => x.ColumnName)
.ToList();
int currentNumber = 1;
foreach (var group in orderedItems)
{
foreach (var item in group)
{
item.ColumnNumber = currentNumber;
}
currentNumber++;
}
}
private int GetNextAvailableColumnNumber()
{
var usedNumbers = MatrixItems.Select(x => x.ColumnNumber).Distinct().OrderBy(x => x).ToList();
int nextNumber = 1;
foreach (var number in usedNumbers)
{
if (number > nextNumber)
return nextNumber;
nextNumber = number + 1;
}
return nextNumber;
}
[RelayCommand]
private void ExportToExcel()
{
var saveDialog = new Microsoft.Win32.SaveFileDialog
{
DefaultExt = ".xlsx",
Filter = "Excel files (*.xlsx)|*.xlsx",
InitialDirectory = EstadoPersistente.Instance.directorio,
FileName = "MatrixExport.xlsx"
};
if (saveDialog.ShowDialog() == true)
{
try
{
using (var workbook = new XLWorkbook())
{
var worksheet = workbook.Worksheets.Add("Matrix");
// Write headers
int colIndex = 1;
worksheet.Cell(1, colIndex++).Value = "Image";
var columns = MatrixItems
.GroupBy(x => x.ColumnNumber)
.OrderBy(g => g.Key)
.Select(g => g.First())
.ToList();
foreach (var col in columns)
{
worksheet.Cell(1, colIndex++).Value = col.ColumnName;
}
// Write data
int rowIndex = 2;
foreach (var row in MatrixRows)
{
colIndex = 1;
worksheet.Cell(rowIndex, colIndex++).Value = row["Image"];
foreach (var col in columns)
{
var value = row.ContainsKey(col.ColumnNumber.ToString())
? row[col.ColumnNumber.ToString()]
: string.Empty;
worksheet.Cell(rowIndex, colIndex++).Value = value;
}
rowIndex++;
}
// Format headers
var headerRow = worksheet.Row(1);
headerRow.Style.Font.Bold = true;
headerRow.Style.Fill.BackgroundColor = XLColor.LightGray;
headerRow.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
// Auto-fit columns
worksheet.Columns().AdjustToContents();
workbook.SaveAs(saveDialog.FileName);
}
MessageBox.Show("Export completed successfully", "Success", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Error exporting to Excel: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
[RelayCommand]
private async Task AICorrectMatrix()
{
try
{
if (_llmService == null)
{
MessageBox.Show("LLM Service not initialized", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
var columnNames = MatrixItems
.Select(x => x.ColumnName)
.Distinct()
.ToList();
var dialog = new ColumnSelectionDialog(columnNames);
if (dialog.ShowDialog() != true) return;
var sourceColumn = dialog.SelectedSourceColumn;
var targetColumn = dialog.SelectedTargetColumn;
var sourceLanguage = dialog.SelectedSourceLanguage;
var targetLanguage = dialog.SelectedTargetLanguage;
// Group items by image and process each row
var itemsByImage = MatrixItems.GroupBy(x => x.Source_Image);
int batchSize = 10;
int processedCount = 0;
foreach (var imageGroup in itemsByImage)
{
var textPairs = new List<(string Source, string Target)>();
var itemsToUpdate = new List<(MatrixItem SourceItem, MatrixItem TargetItem)>();
// Create pairs for each row
var sourceItems = imageGroup
.Where(x => x.ColumnName == sourceColumn)
.OrderBy(x => x.Copy_Number);
var targetItems = imageGroup
.Where(x => x.ColumnName == targetColumn)
.OrderBy(x => x.Copy_Number);
foreach (var (source, target) in sourceItems.Zip(targetItems, (s, t) => (s, t)))
{
if (!string.IsNullOrWhiteSpace(source.Value) && !string.IsNullOrWhiteSpace(target.Value))
{
textPairs.Add((source.Value, target.Value));
itemsToUpdate.Add((source, target));
}
}
// Process in batches
for (int i = 0; i < textPairs.Count; i += batchSize)
{
var batchPairs = textPairs.Skip(i).Take(batchSize).ToList();
var batchItems = itemsToUpdate.Skip(i).Take(batchSize).ToList();
var correctedPairs = await _llmService.ProcessTextBatch(batchPairs, sourceLanguage, targetLanguage);
// Update values
for (int j = 0; j < correctedPairs.Count; j++)
{
batchItems[j].SourceItem.Value = correctedPairs[j].Source;
batchItems[j].TargetItem.Value = correctedPairs[j].Target;
processedCount += 2;
}
}
}
UpdateMatrixPreview();
MessageBox.Show($"AI Correction completed. Processed {processedCount} items.",
"Success", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
MessageBox.Show($"Error during AI correction: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
~MatrixPreviewViewModel()
{
_llmService?.Dispose();
}
}
}