Agregado exportacion a Excel desde la ventana de Analizar la Matriz. Agregada la correccion de las columnas de descripcion.

This commit is contained in:
Miguel 2025-02-15 22:38:12 +01:00
parent 3a2e87cd75
commit 8573d942c4
12 changed files with 1437 additions and 76 deletions

View File

@ -59,6 +59,7 @@
<None Remove="Images\base.png" />
<None Remove="motor2.png" />
<None Remove="tank.png" />
<None Remove="IA\appsettings.json" />
</ItemGroup>
<ItemGroup>
@ -81,6 +82,7 @@
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.9.0.5494" />
<PackageReference Include="Emgu.CV.UI" Version="4.9.0.5494" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.6.1" />
<PackageReference Include="LanguageDetection" Version="1.2.0" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc4.5" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
@ -142,6 +144,9 @@
</ItemGroup>
<ItemGroup>
<None Update="IA\appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
@ -157,4 +162,10 @@
</None>
</ItemGroup>
<ItemGroup>
<Content Include="IA\appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

7
IA/appsettings.json Normal file
View File

@ -0,0 +1,7 @@
{
"ApiKeys": {
"OpenAI": "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",
"Groq": "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",
"Grok": "xai-yR7J4s9VIchdjdsaIMP3zzZJBPa98pySDbavh6hEosG3eII9OtQhoKsNUofKUsKrvNUIW15N11BD5cEa"
}
}

436
IA/gtpask.cs Normal file
View File

@ -0,0 +1,436 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using LanguageDetection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
namespace GTPCorrgir
{
public class Opciones
{
public enum LLM_a_Usar
{
OpenAI,
Ollama,
Groq,
Grok
}
public enum modoDeUso
{
Corregir,
Ortografia,
Traducir_a_Ingles,
Traducir_a_Italiano,
Traducir_a_Espanol,
}
public Dictionary<LLM_a_Usar, string> nombreLLM = new Dictionary<LLM_a_Usar, string>
{
{ LLM_a_Usar.Ollama, "Ollama" },
{ LLM_a_Usar.Groq, "Groq" },
{ LLM_a_Usar.Grok, "Grok" },
{ LLM_a_Usar.OpenAI, "OpenAI" },
};
public LLM_a_Usar LLM { get; set; }
public modoDeUso modo { get; set; }
public string nombreDeLLM()
{
return nombreLLM[LLM];
}
public Opciones() { } // Changed from private to public
}
public class ApiSettings
{
public class ApiKeySection
{
public string OpenAI { get; set; }
public string Groq { get; set; }
public string Grok { get; set; }
}
public ApiKeySection ApiKeys { get; set; }
}
public class gtpask : IDisposable
{
private string _openAiApiKey;
private string _groqApiKey;
private string _grokApiKey;
private readonly HttpClient _httpClient;
private bool _disposed;
private readonly LanguageDetector _languageDetector;
private readonly Dictionary<string, string> _languageMap = new Dictionary<string, string>
{
{ "en", "English" },
{ "es", "Spanish" },
{ "it", "Italian" },
{ "pt", "Portuguese" }
};
public string IdiomaDetectado { get; private set; }
public string TextoACorregir { get; set; }
public string TextoCorregido { get; private set; }
public string TextodeSistema { get; set; }
public gtpask()
{
try
{
_httpClient = new HttpClient();
_languageDetector = new LanguageDetector();
_languageDetector.AddLanguages("en", "es", "it", "pt");
LoadApiKeys();
InitializeHttpClient();
}
catch (Exception ex)
{
throw new ApplicationException("Failed to initialize gtpask", ex);
}
}
private void LoadApiKeys()
{
try
{
string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "IA/appsettings.json");
if (!File.Exists(configPath))
{
throw new FileNotFoundException("Configuration file (appsettings.json) not found.");
}
string jsonContent = File.ReadAllText(configPath);
var settings = JsonConvert.DeserializeObject<ApiSettings>(jsonContent);
_openAiApiKey = settings?.ApiKeys?.OpenAI;
_groqApiKey = settings?.ApiKeys?.Groq;
_grokApiKey = settings?.ApiKeys?.Grok;
ValidateApiKeys();
}
catch (Exception ex)
{
throw new ApplicationException("Failed to load API keys", ex);
}
}
private void ValidateApiKeys()
{
var missingKeys = new List<string>();
if (string.IsNullOrEmpty(_openAiApiKey)) missingKeys.Add("OpenAI");
if (string.IsNullOrEmpty(_groqApiKey)) missingKeys.Add("Groq");
if (string.IsNullOrEmpty(_grokApiKey)) missingKeys.Add("Grok");
if (missingKeys.Any())
{
string missingKeysStr = string.Join(", ", missingKeys);
throw new ApplicationException($"Missing API keys: {missingKeysStr}");
}
}
private void InitializeHttpClient()
{
_httpClient.Timeout = TimeSpan.FromSeconds(30);
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
private bool DetectarIdioma()
{
try
{
string detectedLanguageCode = _languageDetector.Detect(TextoACorregir);
IdiomaDetectado = _languageMap.GetValueOrDefault(detectedLanguageCode, "Desconocido");
return IdiomaDetectado != "Desconocido";
}
catch (Exception ex)
{
return false;
}
}
private async Task ProcesarTextoConLLM( Opciones Modelo)
{
try
{
string respuestaLLM;
switch (Modelo.LLM)
{
case Opciones.LLM_a_Usar.OpenAI:
respuestaLLM = await CallOpenAiApi();
break;
case Opciones.LLM_a_Usar.Ollama:
respuestaLLM = await CallOllamaApi();
break;
case Opciones.LLM_a_Usar.Groq:
respuestaLLM = await CallGroqAiApi();
break;
case Opciones.LLM_a_Usar.Grok:
respuestaLLM = await CallGrokApi();
break;
default:
throw new ArgumentException("LLM no válido");
}
if (string.IsNullOrEmpty(respuestaLLM))
{
throw new ApplicationException("No se recibió respuesta del LLM");
}
TextoCorregido = respuestaLLM;
}
catch (Exception ex)
{
throw;
}
}
private async Task SimularCorreccion()
{
await Task.Delay(1000);
TextoCorregido = "Texto simulado de prueba";
}
private async Task<string> CallGrokApi()
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_grokApiKey}");
var requestData = new
{
messages = new[]
{
new { role = "system", content = TextodeSistema },
new { role = "user", content = TextoACorregir }
},
model = "grok-beta",
stream = false,
temperature = 0
};
return await EnviarSolicitudLLM("https://api.x.ai/v1/chat/completions", requestData);
}
catch (Exception ex)
{
throw;
}
}
private async Task<string> CallOllamaApi()
{
try
{
var requestData = new
{
model = "llama3.2:latest",
messages = new[]
{
new { role = "system", content = TextodeSistema },
new { role = "user", content = TextoACorregir }
},
stream = false
};
return await EnviarSolicitudLLM("http://127.0.0.1:11434/api/chat", requestData);
}
catch (Exception ex)
{
throw;
}
}
private async Task<string> CallOpenAiApi()
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_openAiApiKey}");
var requestData = new
{
model = "gpt-4o-mini",
messages = new[]
{
new { role = "system", content = TextodeSistema },
new { role = "user", content = TextoACorregir }
}
};
return await EnviarSolicitudLLM("https://api.openai.com/v1/chat/completions", requestData);
}
catch (Exception ex)
{
throw;
}
}
private async Task<string> CallGroqAiApi()
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_groqApiKey}");
var requestData = new
{
model = "llama-3.2-3b-preview",
messages = new[]
{
new { role = "system", content = TextodeSistema },
new { role = "user", content = TextoACorregir }
},
max_tokens = 2048,
stream = false
};
return await EnviarSolicitudLLM("https://api.groq.com/openai/v1/chat/completions", requestData);
}
catch (Exception ex)
{
throw;
}
}
private async Task<string> EnviarSolicitudLLM(string endpoint, object requestData)
{
try
{
var content = new StringContent(
JsonConvert.SerializeObject(requestData),
Encoding.UTF8,
"application/json"
);
using var response = await _httpClient.PostAsync(endpoint, content);
var responseContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Error en la solicitud HTTP: {response.StatusCode} - {responseContent}"
);
}
var data = JsonConvert.DeserializeObject<dynamic>(responseContent);
// Manejar diferentes formatos de respuesta según el LLM
if (endpoint.Contains("ollama"))
{
if (data.done == true && data.message != null)
{
return data.message.content;
}
throw new ApplicationException("Formato de respuesta de Ollama inválido");
}
else // OpenAI, Groq, Grok
{
if (data.choices != null && data.choices.Count > 0)
{
return data.choices[0].message.content;
}
throw new ApplicationException("No se encontró contenido en la respuesta del LLM");
}
}
catch (Exception ex)
{
throw;
}
}
public async Task CorregirTexto()
{
try
{
if (string.IsNullOrEmpty(TextoACorregir))
{
throw new ArgumentException("TextoACorregir cannot be null or empty");
}
if (string.IsNullOrEmpty(TextodeSistema))
{
throw new ArgumentException("TextodeSistema cannot be null or empty");
}
var opciones = new Opciones { LLM = Opciones.LLM_a_Usar.Grok };
await ProcesarTextoConLLM(opciones);
}
catch (Exception ex)
{
throw new LLMException("Error during text correction", ex);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_httpClient?.Dispose();
}
_disposed = true;
}
}
~gtpask()
{
Dispose(false);
}
}
// Clase auxiliar para manejar excepciones específicas de la aplicación
public class LLMException : Exception
{
public LLMException(string message) : base(message) { }
public LLMException(string message, Exception innerException) : base(message, innerException) { }
}
// Clase auxiliar para validación
public static class Validations
{
public static void ValidateNotNull(object value, string paramName)
{
if (value == null)
{
throw new ArgumentNullException(paramName);
}
}
public static void ValidateNotNullOrEmpty(string value, string paramName)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Value cannot be null or empty", paramName);
}
}
}
}

View File

@ -0,0 +1,147 @@
import pandas as pd
import os
import logging
import json
import PyLibrary.funciones_comunes as fc
from openai import OpenAI
from openai_api_key import openai_api_key
# Definir el logger a nivel de módulo
logger = None
def corregir_texto_batch(text_pairs, logger):
"""
Corrige errores de OCR en lotes de pares de texto usando GPT-4.
Cada par consiste en dos versiones del mismo texto en diferentes idiomas.
Args:
text_pairs: Lista de tuplas (texto1, texto2) donde cada texto es una lectura OCR
logger: Logger para registrar el proceso
"""
client = OpenAI(api_key=openai_api_key())
system_prompt = """You are an OCR correction specialist. For each pair of texts, which are OCR readings of the same text in different languages, analyze and correct any obvious OCR errors.
Pay special attention to:
- Incorrectly joined words (missing spaces)
- Wrong character recognition (0 vs O, 1 vs I, etc.)
- Extra or missing characters
Return the corrected versions maintaining the general structure and meaning.
Input format: List of text pairs
Expected output format: List of corrected pairs in the same order
Example input: [["PULSANTE DI STARTNASTRI", "START PUSHBUTTONTTOP"]]
Example output: [["PULSANTE DI START NASTRI", "START PUSH BUTTON TOP"]]"""
try:
# Convertir los pares de texto a formato JSON
request_payload = json.dumps({"pairs": text_pairs})
logger.info(f"Solicitando corrección para el lote de textos:\n{request_payload}")
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": request_payload},
],
max_tokens=2000,
temperature=0.3,
)
# Procesar la respuesta
corrections = json.loads(response.choices[0].message.content)
logger.info(f"Correcciones recibidas:\n{corrections}")
return corrections["pairs"]
except Exception as e:
logger.error(f"Error en la corrección por lotes: {str(e)}")
return None
def procesar_archivo(config, archivo_entrada):
"""
Procesa el archivo Excel con los textos OCR y genera un nuevo archivo con las correcciones.
"""
logger.info(f"Iniciando procesamiento de {archivo_entrada}")
try:
# Leer el archivo de entrada
df = fc.read_dataframe_with_cleanup_retries(archivo_entrada)
if config.columna_origen1 not in df.columns or config.columna_origen2 not in df.columns:
logger.error(f"Columnas requeridas no encontradas en el archivo")
print(f"Error: Las columnas {config.columna_origen1} y/o {config.columna_origen2} no existen en el archivo")
return
# Filtrar filas donde ambas columnas tienen contenido
df_filtered = df.dropna(subset=[config.columna_origen1, config.columna_origen2])
df_filtered = df_filtered[
(df_filtered[config.columna_origen1].astype(str).str.strip() != '') &
(df_filtered[config.columna_origen2].astype(str).str.strip() != '')
]
# Crear columnas para textos corregidos
df[f"{config.columna_origen1}_Corregido"] = df[config.columna_origen1]
df[f"{config.columna_origen2}_Corregido"] = df[config.columna_origen2]
# Procesar en lotes de 10 pares
batch_size = 10
total_rows = len(df_filtered)
progress_bar = fc.ProgressBar(
total_rows, prefix="Procesando textos:", suffix="Completado"
)
for i in range(0, total_rows, batch_size):
batch_df = df_filtered.iloc[i:i+batch_size]
# Preparar pares de textos para el lote
text_pairs = [
[str(row[config.columna_origen1]).strip(), str(row[config.columna_origen2]).strip()]
for _, row in batch_df.iterrows()
]
# Obtener correcciones para el lote
corrections = corregir_texto_batch(text_pairs, logger)
if corrections:
# Aplicar correcciones al DataFrame original
for j, correction in enumerate(corrections):
idx = batch_df.index[j]
df.at[idx, f"{config.columna_origen1}_Corregido"] = correction[0]
df.at[idx, f"{config.columna_origen2}_Corregido"] = correction[1]
progress_bar.update(min(i + batch_size, total_rows))
progress_bar.finish()
# Guardar resultados
output_path = config.get_output_path()
fc.save_dataframe_with_retries(df, output_path)
logger.info(f"Archivo procesado y guardado en: {output_path}")
print(f"\nArchivo procesado y guardado en: {output_path}")
print(f"Se procesaron {total_rows} pares de texto.")
except Exception as e:
logger.error(f"Error durante el procesamiento: {str(e)}")
print(f"Error durante el procesamiento: {str(e)}")
def run(config):
global logger
logger = fc.configurar_logger(config.work_dir)
script_name = os.path.basename(__file__)
print(f"\rIniciando: {script_name}\r")
# Solicitar archivo de entrada
from tkinter import filedialog
archivo_entrada = filedialog.askopenfilename(
title="Seleccione el archivo con textos OCR",
filetypes=[("Excel files", "*.xls*")]
)
if archivo_entrada:
procesar_archivo(config, archivo_entrada)
else:
print("No se seleccionó ningún archivo para procesar.")
if __name__ == "__main__":
import menu_ocr_correction
menu_ocr_correction.main()

View File

@ -80,7 +80,7 @@ namespace CtrEditor
public ICommand TBMultiPageExtractTagsCommand { get; }
public ICommand TBMultiPageAnalizeCommand { get; }
public ICommand TBAnalyzeMatrixCommand { get; }
public ICommand TBMultiPageMatrixCommand { get; }
// Evento que se dispara cuando se selecciona una nueva imagen
public event EventHandler<string> ImageSelected;
@ -112,6 +112,8 @@ namespace CtrEditor
[ObservableProperty]
private ObservableCollection<string> recentDirectories;
private bool inhibitSaveChangesControl;
public ICommand OpenRecentDirectoryCommand { get; private set; }
partial void OnIsSimulationRunningChanged(bool value)
@ -204,7 +206,7 @@ namespace CtrEditor
{
if (value != null)
{
if (HasUnsavedChanges)
if (HasUnsavedChanges && !inhibitSaveChangesControl)
{
var result = MessageBox.Show("There are unsaved changes. Do you want to save them?",
"Save Changes",
@ -257,7 +259,12 @@ namespace CtrEditor
{
VistaFiltrada = CollectionViewSource.GetDefaultView(ObjetosSimulables);
VistaFiltrada.Filter = FiltrarObjetos;
ObjetosSimulables.CollectionChanged += (s, e) => VistaFiltrada.Refresh();
ObjetosSimulables.CollectionChanged += (s, e) =>
{
VistaFiltrada.Refresh();
if (!inhibitSaveChangesControl && e.Action != NotifyCollectionChangedAction.Move)
HasUnsavedChanges = true;
};
}
//
@ -314,6 +321,7 @@ namespace CtrEditor
TBMultiPageExtractTagsCommand = new RelayCommand(MultiPageExtractTagsCommand);
TBMultiPageAnalizeCommand = new RelayCommand(MultiPageAnalizeCommand);
TBAnalyzeMatrixCommand = new RelayCommand(AnalyzeMatrixCommand);
TBMultiPageMatrixCommand = new RelayCommand(MultiPageMatrixCommand);
stopwatch_Sim = new Stopwatch();
stopwatch_Sim.Start();
@ -530,7 +538,7 @@ namespace CtrEditor
SaveStateObjetosSimulables();
}
private async Task WaitForUIUpdateAsync()
public async Task WaitForUIUpdateAsync()
{
await Task.Yield();
Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.ApplicationIdle);
@ -539,6 +547,18 @@ namespace CtrEditor
private async void MultiPageExtractTagsCommand()
{
if (HasUnsavedChanges)
{
var result = MessageBox.Show("There are unsaved changes. Do you want to save them?",
"Save Changes",
MessageBoxButton.YesNoCancel);
if (result == MessageBoxResult.Cancel)
return;
else if (result == MessageBoxResult.Yes)
SaveStateObjetosSimulables();
}
var ImagenesSeleccionadas = new ObservableCollection<string>
{
SelectedImage
@ -552,13 +572,21 @@ namespace CtrEditor
selectPagesWindow.DataContext = selectPagesViewModel;
selectPagesWindow.ShowDialog();
if (selectPagesWindow.DataContext is SelectPagesViewModel dialog && dialog.CloseOK)
foreach (var page in ImagenesSeleccionadas)
{
SelectedImage = page;
await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
ExtraerTags();
}
inhibitSaveChangesControl = true;
try
{
if (selectPagesWindow.DataContext is SelectPagesViewModel dialog && dialog.CloseOK)
foreach (var page in ImagenesSeleccionadas)
{
SelectedImage = page;
await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
ExtraerTags();
}
}
finally
{
inhibitSaveChangesControl = false;
}
}
private async void MultiPageAnalizeCommand()
@ -576,16 +604,29 @@ namespace CtrEditor
selectPagesWindow.DataContext = selectPagesViewModel;
selectPagesWindow.ShowDialog();
SaveStateObjetosSimulables(); // Guarda el estado antes de cambiar la imagen
bool originalHasUnsavedChanges = HasUnsavedChanges;
HasUnsavedChanges = false;
foreach (var page in ImagenesSeleccionadas)
try
{
SelectedImage = page;
await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
AnalizePageCommand();
await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
if (selectPagesWindow.DataContext is SelectPagesViewModel dialog && dialog.CloseOK)
{
SaveStateObjetosSimulables(); // Guarda el estado antes de cambiar la imagen
SaveStateObjetosSimulables(); // Guarda el estado antes de cambiar la imagen
foreach (var page in ImagenesSeleccionadas)
{
SelectedImage = page;
await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
AnalizePageCommand();
await WaitForUIUpdateAsync(); // Espera a que la UI se actualice
SaveStateObjetosSimulables(); // Guarda el estado antes de cambiar la imagen
}
}
}
finally
{
HasUnsavedChanges = originalHasUnsavedChanges;
}
}
@ -1109,6 +1150,51 @@ namespace CtrEditor
matrixPreviewWindow.DataContext = matrixPreviewViewModel;
matrixPreviewWindow.ShowDialog();
}
private async void MultiPageMatrixCommand()
{
if (HasUnsavedChanges)
{
var result = MessageBox.Show("There are unsaved changes. Do you want to save them?",
"Save Changes",
MessageBoxButton.YesNoCancel);
if (result == MessageBoxResult.Cancel)
return;
else if (result == MessageBoxResult.Yes)
SaveStateObjetosSimulables();
}
var ImagenesSeleccionadas = new ObservableCollection<string>
{
SelectedImage
};
StopSimulation();
var selectPagesWindow = new SelectPages();
var selectPagesViewModel = new SelectPagesViewModel();
selectPagesViewModel.Initialize(this, selectPagesWindow, ref ImagenesSeleccionadas);
selectPagesWindow.DataContext = selectPagesViewModel;
selectPagesWindow.ShowDialog();
inhibitSaveChangesControl = true;
try
{
if (selectPagesWindow.DataContext is SelectPagesViewModel dialog && dialog.CloseOK)
{
var matrixPreviewWindow = new MatrixPreviewWindow();
var matrixPreviewViewModel = new MatrixPreviewViewModel();
matrixPreviewViewModel.Initialize(this, matrixPreviewWindow, ImagenesSeleccionadas);
matrixPreviewWindow.DataContext = matrixPreviewViewModel;
matrixPreviewWindow.ShowDialog();
}
}
finally
{
inhibitSaveChangesControl = false;
}
}
}
public class SimulationData
{

View File

@ -135,12 +135,6 @@
<TextBlock Text="Extraer Tags" />
</StackPanel>
</Button>
<Button Command="{Binding TBMultiPageExtractTagsCommand}" ToolTip="Extraer Tags in multiple pages.">
<StackPanel>
<Image Source="Icons/extract.png" Width="24" Height="24" />
<TextBlock Text="Multi Page Extract" />
</StackPanel>
</Button>
<Button Command="{Binding TBMultiPageAnalizeCommand}"
ToolTip="Analyze Tags in multiple pages.">
<StackPanel>
@ -154,6 +148,12 @@
<TextBlock Text="Analyze Matrix" />
</StackPanel>
</Button>
<Button Command="{Binding TBMultiPageMatrixCommand}" ToolTip="Analyze Matrix (Multiple Pages)">
<StackPanel>
<Image Source="Icons/analyze.png" Width="24" Height="24" />
<TextBlock Text="Multi Page Matrix" />
</StackPanel>
</Button>
</ToolBar>
</ToolBarTray>

View File

@ -0,0 +1,91 @@
<Window x:Class="CtrEditor.PopUps.ColumnSelectionDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Selección de Columnas e Idiomas"
Height="400"
Width="500"
WindowStartupLocation="CenterOwner"
ResizeMode="NoResize"
ShowInTaskbar="False">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label x:Name="SourceColumnHeaderLabel"
Grid.Row="0"
Grid.Column="0"
Content="Columna origen:"
Margin="0,0,10,5"/>
<ComboBox Grid.Row="1"
Grid.Column="0"
x:Name="SourceColumnComboBox"
Margin="0,0,10,15"
Height="30"
SelectionChanged="ColumnComboBox_SelectionChanged"/>
<Label Grid.Row="2"
Grid.Column="0"
Content="Idioma origen:"
Margin="0,0,10,5"/>
<ComboBox Grid.Row="3"
Grid.Column="0"
x:Name="SourceLanguageComboBox"
Margin="0,0,10,15"
Height="30"/>
<Label x:Name="TargetColumnHeaderLabel"
Grid.Row="0"
Grid.Column="1"
Content="Columna destino:"
Margin="0,0,0,5"/>
<ComboBox Grid.Row="1"
Grid.Column="1"
x:Name="TargetColumnComboBox"
Margin="0,0,0,15"
Height="30"
SelectionChanged="ColumnComboBox_SelectionChanged"/>
<Label Grid.Row="2"
Grid.Column="1"
Content="Idioma destino:"
Margin="0,0,0,5"/>
<ComboBox Grid.Row="3"
Grid.Column="1"
x:Name="TargetLanguageComboBox"
Margin="0,0,0,15"
Height="30"/>
<StackPanel Grid.Row="7"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0,10,0,0">
<Button Content="Aceptar"
Click="OkButton_Click"
Width="90"
Height="30"
Margin="0,0,10,0"/>
<Button Content="Cancelar"
Click="CancelButton_Click"
Width="90"
Height="30"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,197 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace CtrEditor.PopUps
{
public partial class ColumnSelectionDialog : Window
{
private readonly List<string> _columnNames;
public string SelectedSourceColumn { get; private set; }
public string SelectedTargetColumn { get; private set; }
public string SelectedSourceLanguage { get; private set; }
public string SelectedTargetLanguage { get; private set; }
private static readonly List<string> SupportedLanguages = new List<string>
{
"English",
"Spanish",
"Italian",
"French",
"German",
"Portuguese"
};
private string _sourceColumnLabel = "Idioma 1";
private string _targetColumnLabel = "Idioma 2";
public string SourceColumnLabel
{
get => _sourceColumnLabel;
set
{
_sourceColumnLabel = value;
if (SourceColumnHeaderLabel != null)
SourceColumnHeaderLabel.Content = value;
}
}
public string TargetColumnLabel
{
get => _targetColumnLabel;
set
{
_targetColumnLabel = value;
if (TargetColumnHeaderLabel != null)
TargetColumnHeaderLabel.Content = value;
}
}
public ColumnSelectionDialog(List<string> columnNames)
{
InitializeComponent();
_columnNames = columnNames;
SourceColumnComboBox.ItemsSource = columnNames;
TargetColumnComboBox.ItemsSource = columnNames;
SourceLanguageComboBox.ItemsSource = SupportedLanguages;
TargetLanguageComboBox.ItemsSource = SupportedLanguages;
// Pre-select languages from saved settings
PreSelectLanguages();
// Set default labels
SourceColumnHeaderLabel.Content = SourceColumnLabel;
TargetColumnHeaderLabel.Content = TargetColumnLabel;
}
private void PreSelectLanguages()
{
// First try to load last used column pair
var lastPair = EstadoPersistente.Instance.ColumnPairs.LastOrDefault();
if (lastPair != null &&
_columnNames.Contains(lastPair.SourceColumn) &&
_columnNames.Contains(lastPair.TargetColumn))
{
SourceColumnComboBox.SelectedItem = lastPair.SourceColumn;
TargetColumnComboBox.SelectedItem = lastPair.TargetColumn;
}
// If no previous pair exists or columns not found, try Descrip columns
else
{
var descColumns = _columnNames.Where(c => c.Contains("Descrip")).ToList();
if (descColumns.Any())
{
SourceColumnComboBox.SelectedItem = descColumns.FirstOrDefault();
if (descColumns.Count > 1)
{
TargetColumnComboBox.SelectedItem = descColumns[1];
}
}
}
// Load language mappings
if (SourceColumnComboBox.SelectedItem != null)
{
var sourceMapping = EstadoPersistente.Instance.ColumnLanguages
.FirstOrDefault(m => m.ColumnName == SourceColumnComboBox.SelectedItem.ToString());
if (sourceMapping != null)
{
SourceLanguageComboBox.SelectedItem = sourceMapping.Language;
}
}
if (TargetColumnComboBox.SelectedItem != null)
{
var targetMapping = EstadoPersistente.Instance.ColumnLanguages
.FirstOrDefault(m => m.ColumnName == TargetColumnComboBox.SelectedItem.ToString());
if (targetMapping != null)
{
TargetLanguageComboBox.SelectedItem = targetMapping.Language;
}
}
}
private void ColumnComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
var comboBox = sender as System.Windows.Controls.ComboBox;
if (comboBox?.SelectedItem == null) return;
var mapping = EstadoPersistente.Instance.ColumnLanguages
.FirstOrDefault(m => m.ColumnName == comboBox.SelectedItem.ToString());
if (mapping != null)
{
if (comboBox == SourceColumnComboBox)
SourceLanguageComboBox.SelectedItem = mapping.Language;
else if (comboBox == TargetColumnComboBox)
TargetLanguageComboBox.SelectedItem = mapping.Language;
}
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
if (SourceColumnComboBox.SelectedItem == null ||
TargetColumnComboBox.SelectedItem == null ||
SourceLanguageComboBox.SelectedItem == null ||
TargetLanguageComboBox.SelectedItem == null)
{
MessageBox.Show("Please select both columns and their languages",
"Selection Required",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
SelectedSourceColumn = SourceColumnComboBox.SelectedItem.ToString();
SelectedTargetColumn = TargetColumnComboBox.SelectedItem.ToString();
SelectedSourceLanguage = SourceLanguageComboBox.SelectedItem.ToString();
SelectedTargetLanguage = TargetLanguageComboBox.SelectedItem.ToString();
// Save language mappings
SaveLanguageMappings();
DialogResult = true;
Close();
}
private void SaveLanguageMappings()
{
// Save language mappings
var mappings = EstadoPersistente.Instance.ColumnLanguages;
UpdateMapping(mappings, SelectedSourceColumn, SelectedSourceLanguage);
UpdateMapping(mappings, SelectedTargetColumn, SelectedTargetLanguage);
// Save column pair
var columnPairs = EstadoPersistente.Instance.ColumnPairs;
var lastPair = columnPairs.LastOrDefault();
if (lastPair == null ||
lastPair.SourceColumn != SelectedSourceColumn ||
lastPair.TargetColumn != SelectedTargetColumn)
{
columnPairs.Add(new ColumnPairMapping
{
SourceColumn = SelectedSourceColumn,
TargetColumn = SelectedTargetColumn
});
}
EstadoPersistente.Instance.GuardarEstado();
}
private void UpdateMapping(List<ColumnLanguageMapping> mappings, string columnName, string language)
{
var existing = mappings.FirstOrDefault(m => m.ColumnName == columnName);
if (existing != null)
existing.Language = language;
else
mappings.Add(new ColumnLanguageMapping { ColumnName = columnName, Language = language });
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
}
}

View File

@ -21,7 +21,9 @@
Background="White"
GridLinesVisibility="All"
Margin="5"
ColumnReordered="DataGrid_ColumnReordered">
ColumnReordered="DataGrid_ColumnReordered"
SelectionMode="Extended"
SelectionUnit="Cell">
<DataGrid.Resources>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="LightGray"/>
@ -68,6 +70,14 @@
HorizontalAlignment="Right"
Margin="5"
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<Button Content="AI Correction"
Command="{Binding AICorrectMatrixCommand}"
Margin="5"
Padding="10,5"/>
<Button Content="Export to Excel"
Command="{Binding ExportToExcelCommand}"
Margin="5"
Padding="10,5"/>
<Button Content="Regenerate Matrix"
Command="{Binding RegenerateMatrixCommand}"
Margin="5"

105
Services/LLMService.cs Normal file
View File

@ -0,0 +1,105 @@
using GTPCorrgir;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace CtrEditor.Services
{
public class LLMService
{
private readonly gtpask _llmProcessor;
public LLMService()
{
_llmProcessor = new gtpask();
}
public async Task<string> ProcessText(string text, bool useMarkdown = false)
{
try
{
_llmProcessor.TextoACorregir = text;
// Create system prompt through constructor or initialization
_llmProcessor.TextodeSistema = "You are an OCR correction specialist. Analyze and correct any obvious OCR errors." +
"\nPay special attention to:" +
"\n- Incorrectly joined words (missing spaces)" +
"\n- Wrong character recognition (0 vs O, 1 vs I, etc.)" +
"\n- Extra or missing characters" +
"\nReturn only the corrected text without explanations.";
// Initialize a new instance with the system prompt
await _llmProcessor.CorregirTexto();
return _llmProcessor.TextoCorregido;
}
catch (Exception ex)
{
throw new Exception($"Error processing text with LLM: {ex.Message}", ex);
}
}
public async Task<List<(string Source, string Target)>> ProcessTextBatch(
List<(string Source, string Target)> textPairs,
string sourceLanguage = "Unknown",
string targetLanguage = "Unknown")
{
try
{
var textPairsJson = JsonConvert.SerializeObject(
textPairs.Select(p => new[] { p.Source, p.Target }).ToList()
);
_llmProcessor.TextoACorregir = textPairsJson;
_llmProcessor.TextodeSistema = $@"You are an OCR correction specialist working with {sourceLanguage} and {targetLanguage} texts.
For each pair, the first text is in {sourceLanguage} and the second text is in {targetLanguage}.
Pay special attention to:
- Language-specific characters and accents for both {sourceLanguage} and {targetLanguage}
- Incorrectly joined words (missing spaces)
- Wrong character recognition (0 vs O, 1 vs I, etc.)
- Extra or missing characters
Return the corrected versions in JSON format as a list of pairs.
Input: [[""source text"", ""target text""]]
Expected output format: ```json[[""corrected source"", ""corrected target""]]```";
await _llmProcessor.CorregirTexto();
// Extract JSON content from markdown
string jsonContent = ExtractJsonFromMarkdown(_llmProcessor.TextoCorregido);
if (string.IsNullOrEmpty(jsonContent))
{
throw new Exception("Could not extract JSON content from LLM response");
}
var result = JsonConvert.DeserializeObject<List<string[]>>(jsonContent);
return result.Select(pair => (pair[0].TrimEnd('\n'), pair[1].TrimEnd('\n'))).ToList();
}
catch (Exception ex)
{
throw new Exception($"Error processing text batch with LLM: {ex.Message}", ex);
}
}
private string ExtractJsonFromMarkdown(string markdownText)
{
const string jsonStart = "```json";
const string codeBlockEnd = "```";
var startIndex = markdownText.IndexOf(jsonStart);
if (startIndex == -1) return null;
startIndex += jsonStart.Length;
var endIndex = markdownText.IndexOf(codeBlockEnd, startIndex);
if (endIndex == -1) return null;
return markdownText.Substring(startIndex, endIndex - startIndex).Trim();
}
public void Dispose()
{
_llmProcessor?.Dispose();
}
}
}

View File

@ -6,7 +6,10 @@ 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
{
@ -28,7 +31,9 @@ namespace CtrEditor.PopUps
public string Value { get; set; }
public string Type { get; set; }
public bool IsCloned { get; set; }
public osBase SourceObject { 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
@ -36,6 +41,12 @@ namespace CtrEditor.PopUps
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;
@ -49,21 +60,24 @@ namespace CtrEditor.PopUps
[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)
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>>();
AnalyzeMatrix();
AnalyzeMatrixMultiPage();
}
private void UpdateMatrixPreview()
@ -76,6 +90,9 @@ namespace CtrEditor.PopUps
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)
@ -95,38 +112,45 @@ namespace CtrEditor.PopUps
_dataGrid.Columns.Add(column);
}
// Calcular número de filas necesarias
int maxRows = 1;
var clonedItems = MatrixItems.Where(x => x.IsCloned).ToList();
if (clonedItems.Any())
// Process each image's data
foreach (var imageGroup in itemsByImage)
{
maxRows = clonedItems.Max(x => x.SourceObject is osExtraccionTag tag ? tag.Copy_Number : 0) + 1;
}
// Crear filas con valores
for (int row = 0; row < maxRows; row++)
{
var rowData = new Dictionary<string, string>();
// Agregar valores fijos (no clonados) en todas las filas
foreach (var item in MatrixItems.Where(x => !x.IsCloned))
// Calculate max rows needed for this image
int maxRows = 1;
var clonedItems = imageGroup.Where(x => x.IsCloned).ToList();
if (clonedItems.Any())
{
if (item.ColumnNumber > 0)
{
rowData[item.ColumnNumber.ToString()] = item.Value ?? string.Empty;
}
maxRows = clonedItems.Max(x => x.Copy_Number) + 1;
}
// Agregar valores clonados solo en su fila correspondiente
foreach (var item in MatrixItems.Where(x => x.IsCloned))
// Create rows for this image
for (int row = 0; row < maxRows; row++)
{
if (item.ColumnNumber > 0 && item.SourceObject is osExtraccionTag tag && tag.Copy_Number == 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))
{
rowData[item.ColumnNumber.ToString()] = item.Value ?? string.Empty;
if (item.ColumnNumber > 0)
{
rowData[item.ColumnNumber.ToString()] = item.Value ?? string.Empty;
}
}
}
MatrixRows.Add(rowData);
// 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);
}
}
}
@ -147,8 +171,52 @@ namespace CtrEditor.PopUps
}
}
private void AnalyzeMatrix()
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();
}
private List<MatrixItem> AnalyzePage()
{
var items = new List<MatrixItem>();
var osBuscarCoincidencias_List = _mainViewModel.ObjetosSimulables
.OfType<osBuscarCoincidencias>()
.Where(tag => tag.Show_On_This_Page)
@ -173,7 +241,7 @@ namespace CtrEditor.PopUps
foreach (var tag in osExtraccionTagBaseFix_List)
{
tag.CaptureImageAreaAndDoOCR();
MatrixItems.Add(new MatrixItem
items.Add(new MatrixItem
{
TagName = tag.Nombre,
ColumnName = tag.Collumn_name,
@ -181,7 +249,7 @@ namespace CtrEditor.PopUps
Value = tag.Tag_extract,
Type = "Fixed",
IsCloned = false,
SourceObject = tag
Id = tag.Id
});
}
@ -189,7 +257,7 @@ namespace CtrEditor.PopUps
foreach (var tag in osExtraccionTagBaseGrouped_List)
{
tag.CaptureImageAreaAndDoOCR();
MatrixItems.Add(new MatrixItem
items.Add(new MatrixItem
{
TagName = tag.Nombre,
ColumnName = tag.Collumn_name,
@ -197,7 +265,7 @@ namespace CtrEditor.PopUps
Value = tag.Tag_extract,
Type = $"Grouped ({tag.Id_Search_Templates})",
IsCloned = false,
SourceObject = tag
Id = tag.Id
});
}
@ -205,7 +273,7 @@ namespace CtrEditor.PopUps
foreach (var tag in osExtraccionTagCloned_List)
{
tag.CaptureImageAreaAndDoOCR();
MatrixItems.Add(new MatrixItem
items.Add(new MatrixItem
{
TagName = tag.Nombre,
ColumnName = tag.Collumn_name,
@ -213,20 +281,12 @@ namespace CtrEditor.PopUps
Value = tag.Tag_extract,
Type = "Cloned",
IsCloned = true,
SourceObject = tag
Id = tag.Id,
Copy_Number = tag.Copy_Number // Add this line
});
}
// Ordenar los items por número de columna
var items = MatrixItems.OrderBy(x => x.ColumnNumber).ToList();
MatrixItems.Clear();
foreach (var item in items)
{
MatrixItems.Add(item);
}
ResolveColumnConflicts(); // Analizar y resolver conflictos al cargar
UpdateMatrixPreview();
return items;
}
partial void OnMatrixItemsChanged(ObservableCollection<MatrixItem> value)
@ -249,7 +309,7 @@ namespace CtrEditor.PopUps
}
[RelayCommand]
private void ApplyChanges()
private async void ApplyChanges()
{
// Resolver solo los conflictos reales, mantener el orden existente
var conflicts = MatrixItems
@ -273,16 +333,53 @@ namespace CtrEditor.PopUps
}
}
// Aplicar cambios a los objetos
foreach (var item in MatrixItems)
bool originalHasUnsavedChanges = _mainViewModel.HasUnsavedChanges;
_mainViewModel.HasUnsavedChanges = false;
try
{
if (item.SourceObject is osExtraccionTag tag)
foreach (var imageName in selectedImages)
{
tag.Collumn_name = item.ColumnName;
tag.Collumn_number = item.ColumnNumber;
// 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();
}
}
_mainViewModel.HasUnsavedChanges = true;
finally
{
_mainViewModel.HasUnsavedChanges = originalHasUnsavedChanges;
}
_window.Close();
}
@ -359,5 +456,163 @@ namespace CtrEditor.PopUps
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();
}
}
}

View File

@ -10,6 +10,18 @@ using System.Security.Permissions;
namespace CtrEditor
{
public class ColumnLanguageMapping
{
public string ColumnName { get; set; }
public string Language { get; set; }
}
public class ColumnPairMapping
{
public string SourceColumn { get; set; }
public string TargetColumn { get; set; }
}
internal class EstadoPersistente
{
// Ruta donde se guardará el estado
@ -28,6 +40,10 @@ namespace CtrEditor
public List<string> RecentDirectories { get; set; } = new List<string>();
public List<ColumnLanguageMapping> ColumnLanguages { get; set; } = new List<ColumnLanguageMapping>();
public List<ColumnPairMapping> ColumnPairs { get; set; } = new List<ColumnPairMapping>();
// Propiedad pública con get y set para controlar el acceso a _strDirectorioTrabajo
public string directorio
{