Agregado exportacion a Excel desde la ventana de Analizar la Matriz. Agregada la correccion de las columnas de descripcion.
This commit is contained in:
parent
3a2e87cd75
commit
8573d942c4
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"ApiKeys": {
|
||||
"OpenAI": "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",
|
||||
"Groq": "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",
|
||||
"Grok": "xai-yR7J4s9VIchdjdsaIMP3zzZJBPa98pySDbavh6hEosG3eII9OtQhoKsNUofKUsKrvNUIW15N11BD5cEa"
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
122
MainViewModel.cs
122
MainViewModel.cs
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue