Compare commits

...

6 Commits

Author SHA1 Message Date
Miguel 25810b7e6a Se implementó un sistema de fallback utilizando Grok para explicar errores durante el procesamiento, mejorando la gestión de excepciones. Se ajustó el tiempo de espera del cliente HTTP a 60 segundos y se añadieron métodos para obtener líneas recientes y últimas del log. Además, se optimizó la extracción de contenido estructurado en el modo Claude Web Search. 2025-06-17 13:07:48 +02:00
Miguel a8aca9a82a Se agregó la opción "Claude Web Search" al menú y se implementó la lógica para manejar las solicitudes a la API de Claude, incluyendo la extracción de enlaces de búsqueda. Se mejoró la detección de idioma y se estableció español como predeterminado en casos específicos. Además, se optimizó el procesamiento de respuestas para el modo Claude Web Search. 2025-06-17 12:40:29 +02:00
Miguel a48e64f372 Agregada la opción para utilizar el modelo Claude en la aplicación, incluyendo la implementación de la llamada a su API y la validación de la clave API correspondiente. Se realizaron ajustes en el menú y se mejoró la gestión de logs. 2025-06-17 11:48:15 +02:00
Miguel 6d8f70d15b Implementadas nuevas funcionalidades en el menú contextual, incluyendo opciones para abrir y limpiar el archivo de log, así como una ventana "Acerca de" con información sobre la aplicación. Se mejoró la gestión de recursos de PaddleOCR al cerrar la aplicación. 2025-06-16 22:19:14 +02:00
Miguel bd55330739 Agregada la opción --PreguntaRespuesta para permitir el uso del texto del portapapeles como pregunta directa al LLM, combinando la pregunta y la respuesta en el resultado. También se actualizó la lógica de cierre de la aplicación y se mejoró el procesamiento de respuestas. 2025-06-16 21:35:23 +02:00
Miguel e9102f7e1e Agregadas opciones de traducción a portugués en la aplicación, incluyendo ajustes en el menú y la lógica de procesamiento de texto. 2025-06-16 21:24:03 +02:00
35 changed files with 2272 additions and 100 deletions

26
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net8.0-windows/GTPCorrgir.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/GTPCorrgir.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/GTPCorrgir.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/GTPCorrgir.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@ -20,16 +20,15 @@ namespace GTPCorrgir
bool CorreccionFinalizada = false;
Stopwatch stopwatch = new Stopwatch();
private notificacion notificationWindow;
private readonly int TimeoutSeconds = 60; // Timeout máximo para esperar la respuesta
private readonly int TimeoutSeconds = 60;
private CancellationTokenSource _cancellationTokenSource;
private Window _mainWindow;
KeyboardHelper pasteLogic = new KeyboardHelper();
private Window _mainWindow;
protected override async void OnStartup(StartupEventArgs e)
{
ShutdownMode = ShutdownMode.OnExplicitShutdown; // Añade esta línea
ShutdownMode = ShutdownMode.OnExplicitShutdown;
base.OnStartup(e);
_cancellationTokenSource = new CancellationTokenSource();
@ -81,9 +80,12 @@ namespace GTPCorrgir
case Opciones.modoDeUso.Corregir:
case Opciones.modoDeUso.Ortografia:
case Opciones.modoDeUso.PreguntaRespuesta:
case Opciones.modoDeUso.ClaudeWebSearch:
case Opciones.modoDeUso.Traducir_a_Espanol:
case Opciones.modoDeUso.Traducir_a_Ingles:
case Opciones.modoDeUso.Traducir_a_Italiano:
case Opciones.modoDeUso.Traducir_a_Portugues:
await HandleTextCorrection();
return;
}
@ -107,6 +109,9 @@ namespace GTPCorrgir
}
GTP.TextoACorregir = await ClipboardHelper.GetText();
stopwatch.Start();
ShowCustomNotification("Espera", $"Procesando texto con {Opciones.Instance.nombreDeLLM()}...");
IniciarCronometro();
await ProcessCorreccionWithTimeout();
}
@ -150,9 +155,10 @@ namespace GTPCorrgir
ShowCustomNotification("Se puede pegar",
$"Corrección en: {Math.Round(stopwatch.ElapsedMilliseconds / 1000.0, 1)} s");
if (Opciones.Instance.modo == Opciones.modoDeUso.Corregir || Opciones.Instance.modo == Opciones.modoDeUso.Ortografia ||
Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Espanol || Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Ingles ||
Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Italiano)
if (Opciones.Instance.modo == Opciones.modoDeUso.Corregir || Opciones.Instance.modo == Opciones.modoDeUso.Ortografia ||
Opciones.Instance.modo == Opciones.modoDeUso.PreguntaRespuesta || Opciones.Instance.modo == Opciones.modoDeUso.ClaudeWebSearch ||
Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Espanol || Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Ingles ||
Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Italiano || Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Portugues)
{
if (Opciones.Instance.FuncionesOpcionales == Opciones.funcionesOpcionales.MostrarPopUp)
{
@ -177,7 +183,27 @@ namespace GTPCorrgir
catch (Exception ex)
{
GTP.Log.Log($"Error en el procesamiento final: {ex.Message}");
ShowCustomNotification("Error", "Error al procesar el resultado");
try
{
// Intentar usar el fallback con Grok para este error también
string recentLogs = GTP.Log.GetRecentLogs("Iniciando corrección de texto");
string resultadoFallback = await GTP.ExplicarErrorConGrok(GTP.TextoACorregir ?? "Texto no disponible", recentLogs);
if (!string.IsNullOrEmpty(resultadoFallback))
{
await ClipboardHelper.SetText(resultadoFallback);
ShowCustomNotification("Error explicado", "Error analizado y copiado al portapapeles");
}
else
{
ShowCustomNotification("Error", "Error al procesar el resultado");
}
}
catch (Exception fallbackEx)
{
GTP.Log.Log($"Error en fallback para procesamiento final: {fallbackEx.Message}");
ShowCustomNotification("Error", "Error al procesar el resultado");
}
}
finally
{
@ -189,19 +215,43 @@ namespace GTPCorrgir
catch (OperationCanceledException)
{
GTP.Log.Log("Operación cancelada por timeout");
await Dispatcher.InvokeAsync(() =>
await Dispatcher.InvokeAsync(async () =>
{
ShowCustomNotification("Error", "La operación excedió el tiempo límite");
Application.Current.Shutdown();
try
{
// Intentar usar el fallback con Grok para explicar el timeout
await TryErrorFallbackWithGrok(new TimeoutException("La operación excedió el tiempo límite"));
}
catch (Exception fallbackEx)
{
GTP.Log.Log($"Error en fallback para timeout: {fallbackEx.Message}");
ShowCustomNotification("Error", "La operación excedió el tiempo límite");
}
finally
{
Application.Current.Shutdown();
}
});
}
catch (Exception ex)
{
GTP.Log.Log($"Error no controlado: {ex.Message}");
await Dispatcher.InvokeAsync(() =>
await Dispatcher.InvokeAsync(async () =>
{
ShowCustomNotification("Error", "Se produjo un error inesperado");
Application.Current.Shutdown();
try
{
// Intentar usar el fallback con Grok para explicar el error
await TryErrorFallbackWithGrok(ex);
}
catch (Exception fallbackEx)
{
GTP.Log.Log($"Error en fallback: {fallbackEx.Message}");
ShowCustomNotification("Error", "Se produjo un error inesperado");
}
finally
{
Application.Current.Shutdown();
}
});
}
}
@ -210,6 +260,10 @@ namespace GTPCorrgir
{
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
// Limpiar recursos de PaddleOCR
PaddleOCRManager.Cleanup();
base.OnExit(e);
}
@ -217,7 +271,7 @@ namespace GTPCorrgir
{
try
{
GTP.Log.Log($"Mostrando notificación: {title} - {message}");
// GTP.Log.Log($"Mostrando notificación: {title} - {message}"); // Comentado para limpiar logs
if (notificationWindow == null)
{
notificationWindow = new notificacion();
@ -260,6 +314,66 @@ namespace GTPCorrgir
GTP.Log.Log("Cronómetro detenido");
}
}
/// <summary>
/// Intenta usar Grok como fallback para explicar errores cuando el procesamiento principal falla
/// </summary>
private async Task TryErrorFallbackWithGrok(Exception originalError)
{
try
{
GTP.Log.Log("Iniciando sistema de fallback para explicar error");
// Obtener los logs recientes de la última interacción
string recentLogs = GTP.Log.GetRecentLogs("Iniciando corrección de texto");
if (string.IsNullOrEmpty(recentLogs))
{
GTP.Log.Log("No se pudieron obtener logs recientes para el fallback");
ShowCustomNotification("Error", "Se produjo un error inesperado");
return;
}
GTP.Log.Log("Intentando explicar error con Grok usando logs recientes");
// Usar el método de fallback de Grok para explicar el error
string resultadoFallback = await GTP.ExplicarErrorConGrok(GTP.TextoACorregir ?? "Texto no disponible", recentLogs);
if (!string.IsNullOrEmpty(resultadoFallback))
{
// Si Grok pudo explicar el error, copiar el resultado al portapapeles
await ClipboardHelper.SetText(resultadoFallback);
DetenerCronometro();
ShowCustomNotification("Error explicado", "Error analizado y copiado al portapapeles");
// Mostrar ventana de resultado si está configurado
if (Opciones.Instance.FuncionesOpcionales == Opciones.funcionesOpcionales.MostrarPopUp)
{
var resultadoWindow = new VentanaResultado(resultadoFallback);
resultadoWindow.Show();
await Task.Delay(1000);
}
else
{
// Restaurar ventana y pegar automáticamente
await pasteLogic.RestoreAndSimulatePaste();
}
GTP.Log.Log("Fallback con Grok completado exitosamente");
}
else
{
GTP.Log.Log("Grok no pudo explicar el error, usando notificación de error estándar");
ShowCustomNotification("Error", "Se produjo un error inesperado");
}
}
catch (Exception ex)
{
GTP.Log.Log($"Error en sistema de fallback: {ex.Message}");
throw; // Re-lanzar para que sea manejado por el caller
}
}
}
}

View File

@ -1,11 +1,14 @@
<Window x:Class="GTPCorrgir.ContextMenuWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GTPCorrgir" Title="Menu"
WindowStyle="None" AllowsTransparency="True" Background="Transparent" ShowInTaskbar="False" Topmost="True"
SizeToContent="WidthAndHeight" KeyDown="Window_KeyDown" Deactivated="Window_Deactivated">
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GTPCorrgir"
xmlns:av="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="av"
x:Class="GTPCorrgir.ContextMenuWindow" Title="Menu" WindowStyle="None" AllowsTransparency="True"
Background="Transparent" ShowInTaskbar="False" Topmost="True" SizeToContent="WidthAndHeight"
KeyDown="Window_KeyDown" Deactivated="Window_Deactivated" av:DesignHeight="700">
<Window.Resources>
<local:NullToBooleanConverter x:Key="NullToBooleanConverter" />
<Style x:Key="CloseButtonStyle" TargetType="Button">
<Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="#757575" />
<Setter Property="BorderThickness" Value="0" />
@ -13,7 +16,7 @@
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ControlTemplate TargetType="{x:Type Button}">
<Grid Background="Transparent">
<Border x:Name="border" Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"
@ -38,6 +41,27 @@
</Window.Resources>
<Border Background="#F5F5F5" BorderBrush="#CCCCCC" BorderThickness="1" CornerRadius="3" Margin="5">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Abrir Log" Click="OpenLog_Click">
<MenuItem.Icon>
<TextBlock Text="📄" FontSize="14" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Limpiar Log" Click="ClearLog_Click">
<MenuItem.Icon>
<TextBlock Text="🗑️" FontSize="14" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Acerca de" Click="About_Click">
<MenuItem.Icon>
<TextBlock Text="" FontSize="14" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Border.ContextMenu>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@ -60,12 +84,12 @@
<!-- Contenido principal -->
<Grid Grid.Row="1" Margin="10,0,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="ModeListBox" Height="200" Grid.Row="0" Margin="0,0,0,10">
<ListBox x:Name="ModeListBox" Grid.Row="0" Margin="0,0,0,10">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" Padding="5,3" />
@ -75,7 +99,7 @@
<StackPanel Grid.Row="1" Margin="0,0,0,10">
<TextBlock Text="Modelo LLM:" FontWeight="Bold" Margin="0,0,0,5" />
<ListBox x:Name="LLMListBox" Height="120">
<ListBox x:Name="LLMListBox" Height="205">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" Padding="5,3" />
@ -84,8 +108,8 @@
</ListBox>
</StackPanel>
<Button Grid.Row="2" Content="Aceptar" Height="30" Width="80" HorizontalAlignment="Right"
Click="AcceptButton_Click" Margin="0,10,0,0">
<Button Grid.Row="2" Content="Aceptar" Height="30" Width="80" HorizontalAlignment="Center"
Click="AcceptButton_Click" Margin="0,0,0,0">
<!-- ... resto del estilo del botón ... -->
</Button>
</Grid>

View File

@ -5,6 +5,9 @@ using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Data;
using System.Windows.Controls;
using System.IO;
using System.Diagnostics;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using MessageBox = System.Windows.MessageBox;
@ -43,9 +46,12 @@ namespace GTPCorrgir
new MenuOption { DisplayName = "Corregir texto", Value = Opciones.modoDeUso.Corregir },
new MenuOption { DisplayName = "Revisar ortografía", Value = Opciones.modoDeUso.Ortografia },
new MenuOption { DisplayName = "Chat", Value = Opciones.modoDeUso.Chat },
new MenuOption { DisplayName = "Pregunta-Respuesta", Value = Opciones.modoDeUso.PreguntaRespuesta },
new MenuOption { DisplayName = "Claude Web Search", Value = Opciones.modoDeUso.ClaudeWebSearch },
new MenuOption { DisplayName = "Traducir a inglés", Value = Opciones.modoDeUso.Traducir_a_Ingles },
new MenuOption { DisplayName = "Traducir a italiano", Value = Opciones.modoDeUso.Traducir_a_Italiano },
new MenuOption { DisplayName = "Traducir a español", Value = Opciones.modoDeUso.Traducir_a_Espanol },
new MenuOption { DisplayName = "Traducir a portugués", Value = Opciones.modoDeUso.Traducir_a_Portugues },
new MenuOption { DisplayName = "OCR a texto", Value = Opciones.modoDeUso.OCRaTexto }
};
@ -54,7 +60,8 @@ namespace GTPCorrgir
new MenuOption { DisplayName = "OpenAI", Value = Opciones.LLM_a_Usar.OpenAI },
new MenuOption { DisplayName = "Ollama", Value = Opciones.LLM_a_Usar.Ollama },
new MenuOption { DisplayName = "Groq", Value = Opciones.LLM_a_Usar.Groq },
new MenuOption { DisplayName = "Grok", Value = Opciones.LLM_a_Usar.Grok }
new MenuOption { DisplayName = "Grok", Value = Opciones.LLM_a_Usar.Grok },
new MenuOption { DisplayName = "Claude", Value = Opciones.LLM_a_Usar.Claude }
};
InitializeComponent();
@ -122,14 +129,16 @@ namespace GTPCorrgir
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
// Cerrar toda la aplicación cuando se hace clic en el botón de cerrar
System.Windows.Application.Current.Shutdown();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
this.Close();
// Cerrar toda la aplicación cuando se presiona Esc
System.Windows.Application.Current.Shutdown();
}
}
@ -151,6 +160,7 @@ namespace GTPCorrgir
UpdateOptionsAndClose(selectedMode, selectedLLM);
}
}
private void UpdateOptionsAndClose(MenuOption selectedMode, MenuOption selectedLLM)
{
optionsSelected = true;
@ -158,6 +168,12 @@ namespace GTPCorrgir
var modeValue = (Opciones.modoDeUso)selectedMode.Value;
var llmValue = (Opciones.LLM_a_Usar)selectedLLM.Value;
// Si se selecciona Claude Web Search, forzar el uso de Claude
if (modeValue == Opciones.modoDeUso.ClaudeWebSearch)
{
llmValue = Opciones.LLM_a_Usar.Claude;
}
Opciones.Instance.modo = modeValue;
Opciones.Instance.LLM = llmValue;
@ -166,5 +182,198 @@ namespace GTPCorrgir
// Eliminar DialogResult
this.Close();
}
// Manejadores del menú contextual
private void OpenLog_Click(object sender, RoutedEventArgs e)
{
try
{
string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logfile.log");
if (File.Exists(logFilePath))
{
// Intentar abrir con el programa predeterminado
Process.Start(new ProcessStartInfo
{
FileName = logFilePath,
UseShellExecute = true
});
}
else
{
MessageBox.Show("El archivo de log no existe aún.", "Información",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al abrir el archivo de log: {ex.Message}", "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ClearLog_Click(object sender, RoutedEventArgs e)
{
try
{
var result = MessageBox.Show(
"¿Está seguro de que desea limpiar el archivo de log?\nEsta acción no se puede deshacer.",
"Confirmar limpieza de log",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logfile.log");
if (File.Exists(logFilePath))
{
try
{
// Obtener todos los listeners de tipo TextWriterTraceListener
var textWriterListeners = new List<System.Diagnostics.TextWriterTraceListener>();
foreach (System.Diagnostics.TraceListener listener in System.Diagnostics.Trace.Listeners)
{
if (listener is System.Diagnostics.TextWriterTraceListener textListener)
{
textWriterListeners.Add(textListener);
}
}
// Cerrar y remover temporalmente los listeners
foreach (var listener in textWriterListeners)
{
listener.Flush();
listener.Close();
System.Diagnostics.Trace.Listeners.Remove(listener);
}
// Esperar un momento para asegurar que el archivo se libere
System.Threading.Thread.Sleep(100);
// Limpiar el archivo
File.WriteAllText(logFilePath, string.Empty);
// Recrear los listeners
foreach (var listener in textWriterListeners)
{
var newListener = new System.Diagnostics.TextWriterTraceListener(logFilePath);
System.Diagnostics.Trace.Listeners.Add(newListener);
}
// Reactivar AutoFlush
System.Diagnostics.Trace.AutoFlush = true;
MessageBox.Show("El archivo de log ha sido limpiado correctamente.", "Éxito",
MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (UnauthorizedAccessException)
{
MessageBox.Show(
"No se puede acceder al archivo de log. Puede que esté siendo usado por otro proceso.\n" +
"Intente cerrar todas las instancias de la aplicación y vuelva a intentarlo.",
"Acceso denegado",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
catch (IOException ioEx)
{
MessageBox.Show(
$"Error de E/S al acceder al archivo de log:\n{ioEx.Message}\n\n" +
"El archivo puede estar siendo usado por otro proceso.",
"Error de archivo",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
else
{
MessageBox.Show("El archivo de log no existe.", "Información",
MessageBoxButton.OK, MessageBoxImage.Information);
}
}
}
catch (Exception ex)
{
MessageBox.Show($"Error al limpiar el archivo de log: {ex.Message}", "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void About_Click(object sender, RoutedEventArgs e)
{
var aboutDialog = new Window
{
Title = "Acerca de GTPCorregir",
Width = 400,
Height = 300,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = this,
ResizeMode = ResizeMode.NoResize,
ShowInTaskbar = false,
WindowStyle = WindowStyle.ToolWindow
};
var stackPanel = new StackPanel { Margin = new Thickness(20) };
stackPanel.Children.Add(new System.Windows.Controls.TextBlock
{
Text = "GTPCorregir",
FontSize = 18,
FontWeight = FontWeights.Bold,
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
Margin = new Thickness(0, 0, 0, 10)
});
stackPanel.Children.Add(new System.Windows.Controls.TextBlock
{
Text = "Herramienta de corrección de texto con IA",
FontSize = 12,
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
Margin = new Thickness(0, 0, 0, 15)
});
stackPanel.Children.Add(new System.Windows.Controls.TextBlock
{
Text = "• Corrección de ortografía y gramática",
Margin = new Thickness(0, 2, 0, 2)
});
stackPanel.Children.Add(new System.Windows.Controls.TextBlock
{
Text = "• Traducción a múltiples idiomas",
Margin = new Thickness(0, 2, 0, 2)
});
stackPanel.Children.Add(new System.Windows.Controls.TextBlock
{
Text = "• OCR con PaddleOCR",
Margin = new Thickness(0, 2, 0, 2)
});
stackPanel.Children.Add(new System.Windows.Controls.TextBlock
{
Text = "• Soporte para múltiples LLMs",
Margin = new Thickness(0, 2, 0, 2)
});
stackPanel.Children.Add(new System.Windows.Controls.TextBlock
{
Text = $"Versión: {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}",
FontSize = 10,
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
Margin = new Thickness(0, 20, 0, 0)
});
var closeButton = new System.Windows.Controls.Button
{
Content = "Cerrar",
Width = 80,
Height = 25,
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
Margin = new Thickness(0, 15, 0, 0)
};
closeButton.Click += (s, args) => aboutDialog.Close();
stackPanel.Children.Add(closeButton);
aboutDialog.Content = stackPanel;
aboutDialog.ShowDialog();
}
}
}

Binary file not shown.

View File

@ -0,0 +1,623 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>PaddleOCRSharp</name>
</assembly>
<members>
<member name="T:PaddleOCRSharp.EngineBase">
<summary>
Base class for engine objects
</summary>
</member>
<member name="P:PaddleOCRSharp.EngineBase.PaddleOCRdllPath">
<summary>
Custom loading path for PaddleOCR.dll, default is empty. If specified, it needs to be assigned before engine instantiation.
</summary>
</member>
<member name="M:PaddleOCRSharp.EngineBase.#ctor">
<summary>
Initialization
</summary>
</member>
<member name="M:PaddleOCRSharp.EngineBase.GetDllDirectory">
<summary>
Get the current path of the program
</summary>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.EngineBase.GetRootDirectory">
<summary>
Get the current path of the program
</summary>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.EngineBase.ImageToBytes(System.Drawing.Image)">
<summary>
Convert Image to Byte[]
</summary>
<param name="image"></param>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.EngineBase.Dispose">
<summary>
Release memory
</summary>
</member>
<member name="M:PaddleOCRSharp.EngineBase.GetLastError">
<summary>
Get underlying error information
</summary>
<returns></returns>
</member>
<member name="T:PaddleOCRSharp.JsonHelper">
<summary>
Json helper class
</summary>
</member>
<member name="M:PaddleOCRSharp.JsonHelper.DeserializeObject``1(System.String)">
<summary>
Json deserialization
</summary>
<typeparam name="T"></typeparam>
<param name="json"></param>
<returns></returns>
</member>
<member name="T:PaddleOCRSharp.OCRModelConfig">
<summary>
Model configuration object
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRModelConfig.det_infer">
<summary>
det_infer model path
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRModelConfig.cls_infer">
<summary>
cls_infer model path
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRModelConfig.rec_infer">
<summary>
rec_infer model path
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRModelConfig.keys">
<summary>
Full path of ppocr_keys.txt file
</summary>
</member>
<member name="T:PaddleOCRSharp.StructureModelConfig">
<summary>
Table model configuration object
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureModelConfig.table_model_dir">
<summary>
table_model_dir model path
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureModelConfig.table_char_dict_path">
<summary>
Table recognition dictionary
</summary>
</member>
<member name="T:PaddleOCRSharp.OCRParameter">
<summary>
OCR recognition parameters
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.use_gpu">
<summary>
Whether to use GPU; default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.gpu_id">
<summary>
GPU id, effective when using GPU; default 0
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.gpu_mem">
<summary>
Requested GPU memory; default 4000
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.cpu_math_library_num_threads">
<summary>
Number of threads for CPU prediction. When the machine has sufficient cores, the higher this value, the faster the prediction; default 10
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.enable_mkldnn">
<summary>
Whether to use mkldnn library; default true
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det">
<summary>
Whether to perform text detection; default true
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.rec">
<summary>
Whether to perform text recognition; default true
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.cls">
<summary>
Whether to perform text orientation classification; default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.max_side_len">
<summary>
When the input image length and width are greater than 960, the image is scaled proportionally so that the longest side is 960; default 960
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det_db_thresh">
<summary>
Used to filter the binary image predicted by DB. Setting to 0.-0.3 has no significant effect on results; default 0.3
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det_db_box_thresh">
<summary>
DB post-processing threshold for filtering boxes. If detection has missing boxes, this can be reduced accordingly; default 0.5
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det_db_unclip_ratio">
<summary>
Represents the tightness of the text box. Smaller values mean the text box is closer to the text; default 1.6
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.use_dilation">
<summary>
Whether to use dilation on the output map, default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.det_db_score_mode">
<summary>
true: use polygon box to calculate bbox; false: use rectangle box. Rectangle calculation is faster, polygon boxes are more accurate for curved text areas.
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.visualize">
<summary>
Whether to visualize the results. If true, the prediction results will be saved as an ocr_vis.png file in the current directory. Default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.use_angle_cls">
<summary>
Whether to use direction classifier, default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.cls_thresh">
<summary>
Score threshold for direction classifier, default 0.9
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.cls_batch_num">
<summary>
Direction classifier batchsize, default 1
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.rec_batch_num">
<summary>
Recognition model batchsize, default 6
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.rec_img_h">
<summary>
Recognition model input image height, default 48
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.rec_img_w">
<summary>
Recognition model input image width, default 320
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.show_img_vis">
<summary>
Whether to display prediction results, default false
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRParameter.use_tensorrt">
<summary>
When using GPU prediction, whether to enable tensorrt, default false
</summary>
</member>
<member name="T:PaddleOCRSharp.ModifyParameter">
<summary>
OCR modifiable parameters
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_det">
<summary>
Dynamically modify whether to detect. When OCRParameter.det=true, m_det can dynamically turn off the det parameter
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_rec">
<summary>
Dynamically modify whether to recognize. When OCRParameter.rec=true, m_rec can dynamically turn off the rec parameter
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_max_side_len">
<summary>
When the input image length and width are greater than 960, the image is scaled proportionally so that the longest side is 960; default 960
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_det_db_thresh">
<summary>
Used to filter the binary image predicted by DB. Setting to 0.-0.3 has no significant effect on results; default 0.3. Effective when m_det=true
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_det_db_box_thresh">
<summary>
DB post-processing threshold for filtering boxes. If detection has missing boxes, this can be reduced accordingly; default 0.5. Effective when m_det=true
</summary>
</member>
<member name="P:PaddleOCRSharp.ModifyParameter.m_det_db_unclip_ratio">
<summary>
Represents the tightness of the text box. Smaller values mean the text box is closer to the text; default 1.6. Effective when m_det=true
</summary>
</member>
<member name="T:PaddleOCRSharp.OCRResult">
<summary>
OCR recognition result
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRResult.TextBlocks">
<summary>
List of text blocks
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRResult.Text">
<summary>
Recognition result text
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRResult.JsonText">
<summary>
Recognition result text in Json format
</summary>
</member>
<member name="M:PaddleOCRSharp.OCRResult.ToString">
<summary>
Return string format
</summary>
</member>
<member name="T:PaddleOCRSharp.TextBlock">
<summary>
Recognized text block
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.BoxPoints">
<summary>
List of coordinate vertices around the text block
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.Text">
<summary>
Text block content
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.Score">
<summary>
Text recognition confidence
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.cls_score">
<summary>
Angle classification confidence
</summary>
</member>
<member name="P:PaddleOCRSharp.TextBlock.cls_label">
<summary>
Angle classification label
</summary>
</member>
<member name="M:PaddleOCRSharp.TextBlock.ToString">
<summary>
Return string format
</summary>
</member>
<member name="T:PaddleOCRSharp.OCRPoint">
<summary>
Point object
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRPoint.X">
<summary>
X coordinate, in pixels
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRPoint.Y">
<summary>
Y coordinate, in pixels
</summary>
</member>
<member name="M:PaddleOCRSharp.OCRPoint.#ctor">
<summary>
Default constructor
</summary>
</member>
<member name="M:PaddleOCRSharp.OCRPoint.#ctor(System.Int32,System.Int32)">
<summary>
Constructor
</summary>
<param name="x"></param>
<param name="y"></param>
</member>
<member name="M:PaddleOCRSharp.OCRPoint.ToString">
<summary>
Return string format
</summary>
</member>
<member name="T:PaddleOCRSharp.OCRStructureResult">
<summary>
OCR structured recognition result
</summary>
</member>
<member name="M:PaddleOCRSharp.OCRStructureResult.#ctor">
<summary>
Table recognition result
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRStructureResult.RowCount">
<summary>
Number of rows
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRStructureResult.ColCount">
<summary>
Number of columns
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRStructureResult.Cells">
<summary>
List of cells
</summary>
</member>
<member name="P:PaddleOCRSharp.OCRStructureResult.TextBlocks">
<summary>
List of text blocks
</summary>
</member>
<member name="T:PaddleOCRSharp.StructureCells">
<summary>
Cell
</summary>
</member>
<member name="M:PaddleOCRSharp.StructureCells.#ctor">
<summary>
Cell constructor
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureCells.Row">
<summary>
Row number
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureCells.Col">
<summary>
Column number
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureCells.TextBlocks">
<summary>
Text blocks
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureCells.Text">
<summary>
Recognized text
</summary>
</member>
<member name="T:PaddleOCRSharp.PaddleOCREngine">
<summary>
PaddleOCR text recognition engine object
</summary>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.#ctor">
<summary>
Initialize OCR engine object with default parameters
</summary>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.#ctor(PaddleOCRSharp.OCRModelConfig)">
<summary>
Initialize OCR engine object with default parameters
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.#ctor(PaddleOCRSharp.OCRModelConfig,PaddleOCRSharp.OCRParameter)">
<summary>
PaddleOCR recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
<param name="parameter">Recognition parameters, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.#ctor(PaddleOCRSharp.OCRModelConfig,System.String)">
<summary>
PaddleOCR recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
<param name="parameterjson">Recognition parameters in json string format</param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.GetDefaultConfig(System.String)">
<summary>
Get default configuration
</summary>
<param name="rootpath">Root directory</param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectText(System.String)">
<summary>
Perform text recognition on image file
</summary>
<param name="imagefile">Image file</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectText(System.Drawing.Bitmap)">
<summary>
Perform text recognition on image object
</summary>
<param name="image">Image</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectText(System.Byte[])">
<summary>
Text recognition
</summary>
<param name="imagebyte">Image memory stream</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectTextBase64(System.String)">
<summary>
Text recognition
</summary>
<param name="imagebase64">Image base64</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectText(System.IntPtr,System.Int32,System.Int32,System.Int32)">
<summary>
Text recognition
</summary>
<param name="imgPtr">Image memory address</param>
<param name="nWidth">Image width</param>
<param name="nHeight">Image height</param>
<param name="nChannel">Image channel, usually 3 or 1</param>
<returns>OCR recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.ConvertResult(System.IntPtr)">
<summary>
Result parsing
</summary>
<param name="ptrResult"></param>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.DetectStructure(System.Drawing.Bitmap)">
<summary>
Structured text recognition
</summary>
<param name="image">Image</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.getzeroindexs(System.Int32[],System.Int32)">
<summary>
Calculate table splitting
</summary>
<param name="pixellist"></param>
<param name="thresholdtozero"></param>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.ModifyParameter(PaddleOCRSharp.ModifyParameter)">
<summary>
Dynamically modify parameters after initialization
</summary>
<param name="parameter">Modifiable parameter object</param>
<returns>Whether successful, calling before initialization will result in failure</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.EnableDetUseRect(System.Boolean)">
<summary>
Whether to enable rectangular processing of detection results. Single characters are easily detected as diamond shapes, processing them as rectangles can improve recognition accuracy. Only applicable for horizontal text.
</summary>
<param name="enable"></param>
</member>
<member name="M:PaddleOCRSharp.PaddleOCREngine.Dispose">
<summary>
Release object
</summary>
</member>
<member name="T:PaddleOCRSharp.PaddleStructureEngine">
<summary>
PaddleOCR table recognition engine object
</summary>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.#ctor">
<summary>
PaddleStructureEngine recognition engine object initialization
</summary>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.#ctor(PaddleOCRSharp.StructureModelConfig)">
<summary>
PaddleStructureEngine recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.#ctor(PaddleOCRSharp.StructureModelConfig,PaddleOCRSharp.StructureParameter)">
<summary>
PaddleStructureEngine recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
<param name="parameter">Recognition parameters, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.#ctor(PaddleOCRSharp.StructureModelConfig,System.String)">
<summary>
PaddleStructureEngine recognition engine object initialization
</summary>
<param name="config">Model configuration object, if null then default values are used</param>
<param name="parameterjson">Recognition parameters in Json format, if null then default values are used</param>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.GetDefaultConfig(System.String)">
<summary>
Get default configuration
</summary>
<param name="rootpath">Root directory</param>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.StructureDetectFile(System.String)">
<summary>
Perform table text recognition on image file
</summary>
<param name="imagefile">Image file</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.StructureDetect(System.Drawing.Image)">
<summary>
Perform table text recognition on image object
</summary>
<param name="image">Image</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.StructureDetect(System.Byte[])">
<summary>
Perform table text recognition on image Byte array
</summary>
<param name="imagebyte">Image byte array</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.StructureDetectBase64(System.String)">
<summary>
Perform table text recognition on image Base64
</summary>
<param name="imagebase64">Image Base64</param>
<returns>Table recognition result</returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.ConvertResult(System.IntPtr)">
<summary>
Result parsing
</summary>
<param name="ptrResult"></param>
<returns></returns>
</member>
<member name="M:PaddleOCRSharp.PaddleStructureEngine.Dispose">
<summary>
Release object
</summary>
</member>
<member name="T:PaddleOCRSharp.StructureParameter">
<summary>
OCR recognition parameters
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureParameter.table_max_len">
<summary>
When the input image length and width are greater than 488, the image is scaled proportionally, default 488
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureParameter.merge_no_span_structure">
<summary>
Whether to merge empty cells
</summary>
</member>
<member name="P:PaddleOCRSharp.StructureParameter.table_batch_num">
<summary>
Batch recognition quantity
</summary>
</member>
</members>
</doc>

View File

@ -20,13 +20,17 @@
</COMReference>
</ItemGroup>
<ItemGroup>
<None Include="paddleocr\det\inference\.DS_Store" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LanguageDetection" Version="1.2.0" />
<PackageReference Include="Markdown.Xaml" Version="1.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Paddle.Runtime.win_x64" Version="3.0.0" />
<PackageReference Include="PaddleOCRSharp" Version="5.0.0.1" />
<PackageReference Include="System.Drawing.Common" Version="9.0.1" />
<PackageReference Include="Tesseract" Version="5.2.0" />
<PackageReference Include="Tesseract.Drawing" Version="5.2.0" />
</ItemGroup>
<ItemGroup>
@ -39,15 +43,6 @@
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="tessdata\eng.traineddata">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="tessdata\ita.traineddata">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="tessdata\spa.traineddata">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -12,20 +12,98 @@ namespace GTPCorrgir
public class Logger
{
private readonly string _logFilePath;
public Logger()
{
string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logfile.log");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
_logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logfile.log");
Trace.Listeners.Add(new TextWriterTraceListener(_logFilePath));
Trace.AutoFlush = true;
}
public void Log(string message)
{
// Formato de timestamp [AAAA-MM-DD HH:mm:ss]
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
Trace.WriteLine($"[{timestamp}] {message}");
}
}
/// <summary>
/// Obtiene las últimas líneas del log desde una interacción específica
/// </summary>
/// <param name="sinceMessage">Mensaje desde el cual comenzar a leer</param>
/// <returns>String con las líneas del log desde el mensaje especificado</returns>
public string GetRecentLogs(string sinceMessage = "Iniciando corrección de texto")
{
try
{
if (!File.Exists(_logFilePath))
return "No hay archivo de log disponible";
var lines = File.ReadAllLines(_logFilePath);
var recentLines = new List<string>();
bool foundStartPoint = false;
// Buscar desde la última ocurrencia del mensaje especificado
for (int i = lines.Length - 1; i >= 0; i--)
{
if (lines[i].Contains(sinceMessage))
{
foundStartPoint = true;
// Tomar todas las líneas desde este punto hasta el final
for (int j = i; j < lines.Length; j++)
{
recentLines.Add(lines[j]);
}
break;
}
}
if (!foundStartPoint)
{
// Si no encontramos el mensaje específico, tomar las últimas 20 líneas
int startIndex = Math.Max(0, lines.Length - 20);
for (int i = startIndex; i < lines.Length; i++)
{
recentLines.Add(lines[i]);
}
}
return string.Join("\n", recentLines);
}
catch (Exception ex)
{
return $"Error al leer el log: {ex.Message}";
}
}
/// <summary>
/// Obtiene las últimas N líneas del log
/// </summary>
/// <param name="lineCount">Número de líneas a obtener</param>
/// <returns>String con las últimas líneas del log</returns>
public string GetLastLogLines(int lineCount = 20)
{
try
{
if (!File.Exists(_logFilePath))
return "No hay archivo de log disponible";
var lines = File.ReadAllLines(_logFilePath);
int startIndex = Math.Max(0, lines.Length - lineCount);
var lastLines = new List<string>();
for (int i = startIndex; i < lines.Length; i++)
{
lastLines.Add(lines[i]);
}
return string.Join("\n", lastLines);
}
catch (Exception ex)
{
return $"Error al leer el log: {ex.Message}";
}
}
}
}

View File

@ -54,7 +54,7 @@ namespace GTPCorrgir
private void InitializeHttpClient()
{
_httpClient.Timeout = TimeSpan.FromSeconds(30);
_httpClient.Timeout = TimeSpan.FromSeconds(60);
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}

View File

@ -14,7 +14,8 @@ namespace GTPCorrgir
OpenAI,
Ollama,
Groq,
Grok
Grok,
Claude
}
[Flags]
@ -30,11 +31,14 @@ namespace GTPCorrgir
Corregir,
Ortografia,
Chat,
PreguntaRespuesta,
Traducir_a_Ingles,
Traducir_a_Italiano,
Traducir_a_Espanol,
Traducir_a_Portugues,
OCRaTexto,
Menu,
ClaudeWebSearch,
}
public Dictionary<LLM_a_Usar, string> nombreLLM = new Dictionary<LLM_a_Usar, string>
@ -43,6 +47,7 @@ namespace GTPCorrgir
{ LLM_a_Usar.Groq, "Groq" },
{ LLM_a_Usar.Grok, "Grok" },
{ LLM_a_Usar.OpenAI, "OpenAI" },
{ LLM_a_Usar.Claude, "Claude" },
};
private static Opciones _instance;
@ -100,6 +105,8 @@ namespace GTPCorrgir
Opciones.Instance.LLM = Opciones.LLM_a_Usar.Grok;
else if (arg.Contains("OpenAI"))
Opciones.Instance.LLM = Opciones.LLM_a_Usar.OpenAI;
else if (arg.Contains("Claude"))
Opciones.Instance.LLM = Opciones.LLM_a_Usar.Claude;
if (arg.Contains("CtrlA"))
Opciones.Instance.FuncionesOpcionales = Opciones.funcionesOpcionales.CtrlA;
@ -108,6 +115,8 @@ namespace GTPCorrgir
if (arg.Contains("Chat"))
Opciones.Instance.modo = Opciones.modoDeUso.Chat;
if (arg.Contains("PreguntaRespuesta"))
Opciones.Instance.modo = Opciones.modoDeUso.PreguntaRespuesta;
if (arg.Contains("Ortografia"))
Opciones.Instance.modo = Opciones.modoDeUso.Ortografia;
if (arg.Contains("Corregir"))
@ -118,8 +127,12 @@ namespace GTPCorrgir
Opciones.Instance.modo = Opciones.modoDeUso.Traducir_a_Italiano;
if (arg.Contains("Traducir_a_Espanol"))
Opciones.Instance.modo = Opciones.modoDeUso.Traducir_a_Espanol;
if (arg.Contains("Traducir_a_Portugues"))
Opciones.Instance.modo = Opciones.modoDeUso.Traducir_a_Portugues;
if (arg.Contains("OCRaTexto"))
Opciones.Instance.modo = Opciones.modoDeUso.OCRaTexto;
if (arg.Contains("ClaudeWebSearch"))
Opciones.Instance.modo = Opciones.modoDeUso.ClaudeWebSearch;
if (arg.Contains("AutoCopy"))
Opciones.Instance.AutoCopy = true;
if (arg.Contains("Menu"))

View File

@ -13,4 +13,12 @@ Los parametros son:
### Modo de funcionamiento
***********************
* `--Chat` : Para crear una mini ventana que permite hacer chat con los modelos sellecionados.
* `--Correccion` (default ) : Correccion desde el portapapeles y hacia el portapapeles
* `--Corregir` (default) : Corrección desde el portapapeles y hacia el portapapeles
* `--Ortografia` : Revisar ortografía del texto en el portapapeles
* `--PreguntaRespuesta` : Usar el texto del portapapeles como pregunta directa al LLM y devolver pregunta + respuesta
* `--Traducir_a_Ingles` : Traducir texto al inglés
* `--Traducir_a_Italiano` : Traducir texto al italiano
* `--Traducir_a_Espanol` : Traducir texto al español
* `--Traducir_a_Portugues` : Traducir texto al portugués
* `--OCRaTexto` : Capturar pantalla y convertir imagen a texto
* `--Menu` : Mostrar menú de selección de opciones

97
README_FALLBACK.md Normal file
View File

@ -0,0 +1,97 @@
# Sistema de Fallback con Grok para Explicación de Errores
## Descripción
Se ha implementado un sistema de fallback que utiliza Grok para explicar errores cuando el procesamiento principal de la aplicación falla. Esta funcionalidad proporciona al usuario una explicación comprensible de qué salió mal cuando no se puede generar una respuesta normal.
## Funcionamiento
1. **Detección de Error**: Cuando el procesamiento principal falla (por ejemplo, error 529 de Claude por sobrecarga del servidor), el sistema captura la excepción.
2. **Obtención de Logs**: Se obtienen los logs de la última interacción usando el método `GetRecentLogs()` del Logger, que busca desde "Iniciando corrección de texto" hasta el final.
3. **Llamada a Grok**: Se envía a Grok un prompt del sistema que lo instruye como "asistente técnico especializado en explicar errores de software" junto con los logs de error.
4. **Presentación del Resultado**: Si Grok puede explicar el error, se presenta al usuario:
```
[Texto original]
ERROR: [Explicación de Grok]
```
5. **Fallback del Fallback**: Si Grok también falla, se muestra el último mensaje de error encontrado en los logs.
## Archivos Modificados
### Logger.cs
- **Nuevo**: `GetRecentLogs(string sinceMessage)` - Obtiene logs desde un mensaje específico
- **Nuevo**: `GetLastLogLines(int lineCount)` - Obtiene las últimas N líneas del log
- **Modificado**: Almacena la ruta del archivo de log para poder leerlo
### gtpask.cs
- **Nuevo**: `ExplicarErrorConGrok(string originalText, string errorLogs)` - Método principal del fallback
- **Nuevo**: `CallGrokForErrorExplanation(string systemPrompt, string userPrompt)` - Llamada específica a Grok para explicaciones
- **Nuevo**: `GetLastErrorMessage(string errorLogs)` - Extrae el último mensaje de error de los logs
### App.xaml.cs
- **Nuevo**: `TryErrorFallbackWithGrok(Exception originalError)` - Método que coordina el fallback
- **Modificado**: Catch de `ProcessCorreccionWithTimeout()` - Ahora usa el fallback
- **Modificado**: Catch de timeout - Ahora usa el fallback
- **Modificado**: Catch de error en procesamiento final - Ahora usa el fallback
## Configuración
### Prerequisitos
- API key de Grok configurada en `appsettings.json` bajo `ApiKeys.Grok`
### Parámetros de Grok
- **Modelo**: `grok-beta`
- **Temperatura**: `0.3` (para respuestas consistentes)
- **Max Tokens**: `500` (para explicaciones concisas)
- **Stream**: `false`
## Ejemplo de Uso
### Escenario: Error 529 de Claude (servidor sobrecargado)
**Logs de entrada:**
```
[2025-06-17 12:48:42] Iniciando corrección de texto
[2025-06-17 12:48:42] Iniciando proceso de corrección
[2025-06-17 12:48:42] Texto original: Que modelos de SEW en profinet existen?
[2025-06-17 12:48:42] Idioma detectado: Spanish
[2025-06-17 12:48:42] Texto marcado: Que modelos de SEW en profinet existen?
[2025-06-17 12:48:42] Enviando solicitud a https://api.anthropic.com/v1/messages
[2025-06-17 12:49:12] Respuesta recibida: {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}
[2025-06-17 12:49:12] Error en llamada a Claude Web Search API: Error en la solicitud HTTP: 529 - {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}
[2025-06-17 12:49:12] Error no controlado: Error en la solicitud HTTP: 529 - {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}
```
**Resultado esperado:**
```
Que modelos de SEW en profinet existen?
ERROR: El servidor de Claude está actualmente sobrecargado y no puede procesar la solicitud. Esto es un problema temporal del proveedor del servicio. Intente nuevamente en unos minutos.
```
## Características de Seguridad
1. **No Recursivo**: El sistema de fallback se ejecuta solo una vez por error, no recursivamente.
2. **Manejo de Errores**: Si Grok falla, se muestra el error original sin causar fallos adicionales.
3. **Timeouts**: Usa los mismos timeouts configurados para las otras APIs.
4. **Logging**: Todas las operaciones del fallback se registran en el log para debugging.
## Casos de Error Cubiertos
- Errores de API (429, 500, 529, etc.)
- Timeouts de operación
- Errores de procesamiento
- Errores de formato de respuesta
- Errores de conectividad
## Limitaciones
- Requiere que la API key de Grok esté configurada
- Solo funciona si Grok está disponible
- No funciona para errores de inicialización de la aplicación
- Limitado a 500 tokens para mantener respuestas concisas

View File

@ -6,8 +6,9 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Shapes;
using Tesseract;
using PaddleOCRSharp;
using System.Threading.Tasks;
using System.Linq;
using Point = System.Windows.Point;
using Rect = System.Windows.Rect;
using Size = System.Windows.Size;
@ -24,6 +25,7 @@ using System.Windows.Data;
using Binding = System.Windows.Data.Binding;
using Image = System.Windows.Controls.Image;
namespace GTPCorrgir
{
public class ScreenCaptureWindow : Window
@ -35,7 +37,7 @@ namespace GTPCorrgir
private Canvas overlayCanvas;
private readonly Cursor crosshairCursor = Cursors.Cross;
private bool isSelecting;
private TextBlock dimensionsText;
private System.Windows.Controls.TextBlock dimensionsText;
public ScreenCaptureWindow()
{
@ -284,7 +286,7 @@ namespace GTPCorrgir
var screenCapture = CaptureScreen(x, y, width, height);
if (screenCapture == null) return;
// Resto del procesamiento OCR igual que antes
// Procesamiento OCR con PaddleOCR
using (var memoryStream = new MemoryStream())
{
var encoder = new PngBitmapEncoder();
@ -292,14 +294,37 @@ namespace GTPCorrgir
encoder.Save(memoryStream);
memoryStream.Position = 0;
string tesseractPath = GetTesseractExecutablePath();
using (var engine = new TesseractEngine(tesseractPath, "eng", EngineMode.Default))
try
{
using (var img = Pix.LoadFromMemory(memoryStream.ToArray()))
{
var result = engine.Process(img);
string ocrText = result.GetText();
// Convertir a array de bytes
byte[] imageBytes = memoryStream.ToArray();
// Obtener el motor PaddleOCR
var ocrEngine = PaddleOCRManager.GetEngine();
// Usar PaddleOCR para detectar texto
OCRResult result = ocrEngine.DetectText(imageBytes);
string ocrText = "";
if (result != null && result.TextBlocks != null && result.TextBlocks.Count > 0)
{
// Lista de caracteres permitidos (similar a la whitelist de Tesseract)
string allowedChars = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-./";
// Filtrar texto por lista de caracteres permitidos
var filteredBlocks = result.TextBlocks
.Where(block => block.Score > 0.5) // Solo bloques con confianza > 50%
.Select(block => new {
Text = new string(block.Text.Where(c => allowedChars.Contains(c)).ToArray()),
Score = block.Score
})
.OrderByDescending(block => block.Score);
ocrText = string.Join(" ", filteredBlocks.Select(b => b.Text));
}
if (!string.IsNullOrEmpty(ocrText))
{
using (var textProcessor = new OcrTextProcessor())
{
string processedText = await textProcessor.ProcessOcrText(ocrText);
@ -308,6 +333,17 @@ namespace GTPCorrgir
notificationWindow.Show();
}
}
else
{
var notificationWindow = new notificacion();
notificationWindow.UpdateNotification("Sin texto", "No se pudo detectar texto en la imagen seleccionada");
notificationWindow.Show();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error en OCR: {ex.Message}");
MessageBox.Show($"Error en OCR: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
@ -329,20 +365,5 @@ namespace GTPCorrgir
this.Close();
}
}
private string GetTesseractExecutablePath()
{
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string tessDataDir = Path.Combine(baseDir, "tessdata");
if (!Directory.Exists(tessDataDir))
{
throw new DirectoryNotFoundException(
$"No se encontró el directorio tessdata en {tessDataDir}. " +
"Asegúrate de que la carpeta tessdata existe y contiene los archivos de entrenamiento.");
}
return tessDataDir;
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.IO;
using System.Drawing;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows;
using System.Linq;
using PaddleOCRSharp;
namespace GTPCorrgir
{
// Clase auxiliar para gestionar PaddleOCR
public static class PaddleOCRManager
{
private static PaddleOCREngine _engine;
private static bool _initialized = false;
private static readonly object _lockObj = new object();
public static PaddleOCREngine GetEngine()
{
if (!_initialized)
{
lock (_lockObj)
{
if (!_initialized)
{
InitializeEngine();
_initialized = true;
}
}
}
return _engine;
}
private static void InitializeEngine()
{
try
{
string baseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "paddleocr");
OCRModelConfig config = new OCRModelConfig
{
// Rutas a modelos de inferencia
det_infer = Path.Combine(baseDir, "det", "inference"),
cls_infer = Path.Combine(baseDir, "cls", "inference"),
rec_infer = Path.Combine(baseDir, "rec", "inference"),
keys = Path.Combine(baseDir, "keys", "ppocr_keys.txt")
};
OCRParameter parameter = new OCRParameter
{
// Configurar parámetros de OCR
use_angle_cls = true,
cls_thresh = 0.9f,
det_db_thresh = 0.3f,
det_db_box_thresh = 0.6f,
rec_batch_num = 6,
rec_img_h = 48,
enable_mkldnn = true,
use_gpu = false,
cpu_math_library_num_threads = Environment.ProcessorCount
};
_engine = new PaddleOCREngine(config, parameter);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error al inicializar PaddleOCR: {ex.Message}");
// Fallback - inicializar con configuración predeterminada
_engine = new PaddleOCREngine();
}
}
public static void Cleanup()
{
lock (_lockObj)
{
if (_initialized && _engine != null)
{
_engine.Dispose();
_engine = null;
_initialized = false;
}
}
}
}
}

666
gtpask.cs
View File

@ -20,6 +20,7 @@ namespace GTPCorrgir
public string OpenAI { get; set; }
public string Groq { get; set; }
public string Grok { get; set; }
public string Claude { get; set; }
}
public ApiKeySection ApiKeys { get; set; }
@ -30,6 +31,7 @@ namespace GTPCorrgir
private string _openAiApiKey;
private string _groqApiKey;
private string _grokApiKey;
private string _claudeApiKey;
private readonly HttpClient _httpClient;
private bool _disposed;
private readonly LanguageDetector _languageDetector;
@ -90,6 +92,7 @@ namespace GTPCorrgir
_openAiApiKey = settings?.ApiKeys?.OpenAI;
_groqApiKey = settings?.ApiKeys?.Groq;
_grokApiKey = settings?.ApiKeys?.Grok;
_claudeApiKey = settings?.ApiKeys?.Claude;
ValidateApiKeys();
}
@ -107,6 +110,7 @@ namespace GTPCorrgir
if (string.IsNullOrEmpty(_openAiApiKey)) missingKeys.Add("OpenAI");
if (string.IsNullOrEmpty(_groqApiKey)) missingKeys.Add("Groq");
if (string.IsNullOrEmpty(_grokApiKey)) missingKeys.Add("Grok");
if (string.IsNullOrEmpty(_claudeApiKey)) missingKeys.Add("Claude");
if (missingKeys.Any())
{
@ -117,7 +121,7 @@ namespace GTPCorrgir
private void InitializeHttpClient()
{
_httpClient.Timeout = TimeSpan.FromSeconds(30);
_httpClient.Timeout = TimeSpan.FromSeconds(60);
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
@ -172,16 +176,43 @@ namespace GTPCorrgir
return true;
}
string detectedLanguageCode = _languageDetector.Detect(TextoACorregir);
IdiomaDetectado = _languageMap.GetValueOrDefault(detectedLanguageCode, "Desconocido");
// Si el texto es solo una URL, usar español como predeterminado
if (EsSoloURL(TextoACorregir))
{
IdiomaDetectado = _languageMap["es"]; // Español como predeterminado
Log.Log($"Texto es URL, usando idioma predeterminado: {IdiomaDetectado}");
return true;
}
// Si el texto es muy corto, usar español como predeterminado
if (string.IsNullOrWhiteSpace(TextoACorregir) || TextoACorregir.Trim().Length < 10)
{
IdiomaDetectado = _languageMap["es"]; // Español como predeterminado
Log.Log($"Texto muy corto, usando idioma predeterminado: {IdiomaDetectado}");
return true;
}
string detectedLanguageCode = _languageDetector.Detect(TextoACorregir);
// Si no se detectó idioma o es desconocido, usar español como fallback
if (string.IsNullOrEmpty(detectedLanguageCode) || !_languageMap.ContainsKey(detectedLanguageCode))
{
IdiomaDetectado = _languageMap["es"]; // Español como fallback
Log.Log($"Idioma no detectado, usando fallback: {IdiomaDetectado}");
return true;
}
IdiomaDetectado = _languageMap[detectedLanguageCode];
Log.Log($"Idioma detectado: {IdiomaDetectado}");
return IdiomaDetectado != "Desconocido";
return true;
}
catch (Exception ex)
{
Log.Log($"Error al detectar idioma: {ex.Message}");
return false;
// En caso de error, usar español como fallback
IdiomaDetectado = _languageMap["es"];
Log.Log($"Error en detección, usando fallback: {IdiomaDetectado}");
return true;
}
}
@ -189,7 +220,8 @@ namespace GTPCorrgir
{
return Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Ingles ||
Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Italiano ||
Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Espanol;
Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Espanol ||
Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Portugues;
}
private string ObtenerIdiomaObjetivo()
@ -199,6 +231,7 @@ namespace GTPCorrgir
Opciones.modoDeUso.Traducir_a_Ingles => _languageMap["en"],
Opciones.modoDeUso.Traducir_a_Italiano => _languageMap["it"],
Opciones.modoDeUso.Traducir_a_Espanol => _languageMap["es"],
Opciones.modoDeUso.Traducir_a_Portugues => _languageMap["pt"],
_ => throw new ArgumentException("Modo de traducción no válido")
};
}
@ -221,23 +254,37 @@ namespace GTPCorrgir
try
{
string respuestaLLM;
string respuestaCompleta = "";
switch (Opciones.Instance.LLM)
// Si es el modo Claude Web Search, usar el método específico
if (Opciones.Instance.modo == Opciones.modoDeUso.ClaudeWebSearch)
{
case Opciones.LLM_a_Usar.OpenAI:
respuestaLLM = await CallOpenAiApi(textoMarcado);
break;
case Opciones.LLM_a_Usar.Ollama:
respuestaLLM = await CallOllamaApi(textoMarcado);
break;
case Opciones.LLM_a_Usar.Groq:
respuestaLLM = await CallGroqAiApi(textoMarcado);
break;
case Opciones.LLM_a_Usar.Grok:
respuestaLLM = await CallGrokApi(textoMarcado);
break;
default:
throw new ArgumentException("LLM no válido");
var resultadoWebSearch = await CallClaudeWebSearchApiCompleto(textoMarcado);
respuestaLLM = resultadoWebSearch.texto;
respuestaCompleta = resultadoWebSearch.respuestaCompleta;
}
else
{
switch (Opciones.Instance.LLM)
{
case Opciones.LLM_a_Usar.OpenAI:
respuestaLLM = await CallOpenAiApi(textoMarcado);
break;
case Opciones.LLM_a_Usar.Ollama:
respuestaLLM = await CallOllamaApi(textoMarcado);
break;
case Opciones.LLM_a_Usar.Groq:
respuestaLLM = await CallGroqAiApi(textoMarcado);
break;
case Opciones.LLM_a_Usar.Grok:
respuestaLLM = await CallGrokApi(textoMarcado);
break;
case Opciones.LLM_a_Usar.Claude:
respuestaLLM = await CallClaudeApi(textoMarcado);
break;
default:
throw new ArgumentException("LLM no válido");
}
}
if (string.IsNullOrEmpty(respuestaLLM))
@ -245,7 +292,7 @@ namespace GTPCorrgir
throw new ApplicationException("No se recibió respuesta del LLM");
}
ProcesarRespuestaLLM(respuestaLLM);
ProcesarRespuestaLLM(respuestaLLM, respuestaCompleta);
}
catch (Exception ex)
{
@ -254,16 +301,186 @@ namespace GTPCorrgir
}
}
private void ProcesarRespuestaLLM(string respuestaLLM)
private void ProcesarRespuestaLLM(string respuestaLLM, string respuestaCompleta)
{
TextoCorregido = ExtraerValorUnicoJSON(respuestaLLM);
if (TextoCorregido == null)
// Para Claude Web Search, la respuesta ya viene en formato de texto final
if (Opciones.Instance.modo == Opciones.modoDeUso.ClaudeWebSearch)
{
// Extraer contenido estructurado (textos + enlaces)
string contenidoEstructurado = ExtraerEnlacesWebSearch(respuestaCompleta);
if (!string.IsNullOrEmpty(contenidoEstructurado))
{
// Si hay contenido estructurado, usarlo directamente
TextoCorregido = $"{TextoACorregir}\n\n## Respuesta:\n{contenidoEstructurado}";
Log.Log("Claude Web Search: Usando contenido estructurado con textos y enlaces");
}
else
{
// Fallback: intentar extraer JSON primero, si falla usar respuesta completa
string respuestaExtraida = ExtraerValorUnicoJSON(respuestaLLM);
if (respuestaExtraida == null)
{
respuestaExtraida = respuestaLLM;
Log.Log("Claude Web Search: No se encontró JSON, usando respuesta completa");
}
respuestaExtraida = System.Text.RegularExpressions.Regex.Replace(respuestaExtraida, @"\*\*(.*?)\*\*", "$1");
respuestaExtraida = _markdownProcessor.RemoveTechnicalTermMarkers_IgnoreCase(respuestaExtraida).Trim('"');
respuestaExtraida = _markdownProcessor.RemoveDoubleBrackets(respuestaExtraida);
TextoCorregido = $"{TextoACorregir}\n\n{respuestaExtraida}";
Log.Log("Claude Web Search: Usando respuesta fallback");
}
return;
}
// Para otros LLMs, procesamiento normal con JSON requerido
string respuestaExtraidaNormal = ExtraerValorUnicoJSON(respuestaLLM);
if (respuestaExtraidaNormal == null)
{
throw new ApplicationException("Error al extraer el texto corregido de la respuesta JSON");
}
TextoCorregido = _markdownProcessor.RemoveTechnicalTermMarkers_IgnoreCase(TextoCorregido).Trim('"');
TextoCorregido = _markdownProcessor.RemoveDoubleBrackets(TextoCorregido);
respuestaExtraidaNormal = System.Text.RegularExpressions.Regex.Replace(respuestaExtraidaNormal, @"\*\*(.*?)\*\*", "$1");
respuestaExtraidaNormal = _markdownProcessor.RemoveTechnicalTermMarkers_IgnoreCase(respuestaExtraidaNormal).Trim('"');
respuestaExtraidaNormal = _markdownProcessor.RemoveDoubleBrackets(respuestaExtraidaNormal);
// Para el modo Pregunta-Respuesta, combinar pregunta original con la respuesta
if (Opciones.Instance.modo == Opciones.modoDeUso.PreguntaRespuesta)
{
TextoCorregido = $"{TextoACorregir}\n{respuestaExtraidaNormal}";
}
else
{
TextoCorregido = respuestaExtraidaNormal;
}
}
private string ExtraerEnlacesWebSearch(string respuestaCompleta)
{
try
{
if (string.IsNullOrEmpty(respuestaCompleta))
{
Log.Log("ExtraerEnlacesWebSearch: respuestaCompleta está vacía");
return "";
}
Log.Log($"ExtraerEnlacesWebSearch: Procesando respuesta de {respuestaCompleta.Length} caracteres");
var contenidoFormateado = new List<string>();
// Deserializar la respuesta completa para extraer los resultados
var data = JsonConvert.DeserializeObject<dynamic>(respuestaCompleta);
if (data?.content != null)
{
Log.Log($"ExtraerEnlacesWebSearch: Encontrados {data.content.Count} elementos de contenido");
foreach (var contentItem in data.content)
{
string tipoElemento = contentItem.type?.ToString() ?? "";
Log.Log($"ExtraerEnlacesWebSearch: Procesando elemento tipo: {tipoElemento}");
// Procesar elementos de texto con sus citas
if (tipoElemento == "text" && contentItem.text != null)
{
string textoRespuesta = contentItem.text.ToString().Trim();
// Solo procesar textos que no sean muy cortos y tengan contenido útil
if (!string.IsNullOrWhiteSpace(textoRespuesta) && textoRespuesta.Length > 10)
{
Log.Log($"ExtraerEnlacesWebSearch: Texto encontrado: {textoRespuesta.Substring(0, Math.Min(50, textoRespuesta.Length))}...");
// Agregar el texto principal como viñeta
contenidoFormateado.Add($"* {textoRespuesta}");
// Verificar si hay citas asociadas
if (contentItem.citations != null && contentItem.citations.Count > 0)
{
Log.Log($"ExtraerEnlacesWebSearch: Encontradas {contentItem.citations.Count} citas para este texto");
foreach (var citation in contentItem.citations)
{
if (citation.type == "web_search_result_location")
{
string titulo = citation.title?.ToString() ?? "Sin título";
string url = citation.url?.ToString() ?? "";
if (!string.IsNullOrEmpty(url))
{
// Agregar el enlace como sub-viñeta indentada
contenidoFormateado.Add($" * [{titulo}]({url})");
Log.Log($"ExtraerEnlacesWebSearch: Cita agregada - {titulo}");
}
}
}
}
}
}
// También capturar resultados de búsqueda web como fuentes adicionales
else if (tipoElemento == "web_search_tool_result" && contentItem.content != null)
{
Log.Log($"ExtraerEnlacesWebSearch: Procesando resultados de búsqueda web");
var enlacesBusqueda = new List<string>();
foreach (var searchResult in contentItem.content)
{
if (searchResult.type == "web_search_result")
{
string titulo = searchResult.title?.ToString() ?? "Sin título";
string url = searchResult.url?.ToString() ?? "";
string pageAge = searchResult.page_age?.ToString() ?? "";
if (!string.IsNullOrEmpty(url))
{
string enlaceFormateado;
if (!string.IsNullOrEmpty(pageAge))
{
enlaceFormateado = $" * [{titulo}]({url}) - {pageAge}";
}
else
{
enlaceFormateado = $" * [{titulo}]({url})";
}
enlacesBusqueda.Add(enlaceFormateado);
}
}
}
// Si hay resultados de búsqueda adicionales, agregarlos al final
if (enlacesBusqueda.Any())
{
contenidoFormateado.Add("* Fuentes adicionales consultadas:");
contenidoFormateado.AddRange(enlacesBusqueda.Take(5)); // Limitar a 5 para no sobrecargar
}
}
}
}
else
{
Log.Log("ExtraerEnlacesWebSearch: No se encontró contenido en la respuesta");
}
Log.Log($"ExtraerEnlacesWebSearch: Generadas {contenidoFormateado.Count} líneas de contenido formateado");
if (contenidoFormateado.Any())
{
return string.Join("\n", contenidoFormateado);
}
return "";
}
catch (Exception ex)
{
Log.Log($"Error al extraer enlaces de web search: {ex.Message}");
return "";
}
}
private async Task SimularCorreccion()
@ -307,12 +524,26 @@ namespace GTPCorrgir
"You are an engineer working in industrial automation. Your task is to review texts and rewrite them in a simple and concise manner. If you find words enclosed in double brackets [[like this]], preserve them exactly as they appear without any modifications. For all other technical terms, write them normally without adding any brackets or special formatting. Preserve any existing markdown language if present. Please rewrite the following text in " + IdiomaDetectado + " and respond in the following JSON format: { \"Rewritten_text\": \"Your text here\" }. Important: Do not add any new brackets to words that aren't already enclosed in double brackets.",
Opciones.modoDeUso.Ortografia =>
"Please check the following text for spelling errors and provide the corrected version. Do not change the meaning or structure of the sentences. If you find words enclosed in double brackets [[like this]], preserve them exactly as they appear. For all other words, only correct spelling mistakes while preserving technical terms and any markdown language if present. Please write in " + IdiomaDetectado + " and respond in the following JSON format: { \"Rewritten_text\": \"Your text here\" }.",
"Please check the following text for spelling errors and provide the corrected version. Do not change the meaning or structure of the sentences. If you find words enclosed in double brackets [[like this]], preserve them exactly as they appear. For all other words, only correct spelling mistakes while preserving technical terms. Preserve any existing markdown language if present, but do not introduce new markdown styling (such as bold or italics) for emphasis unless it was part of the original input. Please write in " + IdiomaDetectado + " and respond in the following JSON format: { \"Rewritten_text\": \"Your text here\" }.",
Opciones.modoDeUso.PreguntaRespuesta =>
"You are a helpful assistant specialized in industrial automation and technical topics. Please answer the user's question accurately and clearly. If the question contains words in double brackets [[like this]], preserve them exactly as they appear in your response. Please write your answer in " + IdiomaDetectado + " and respond in the following JSON format: { \"Reply_text\": \"Your answer here\" }.",
_ => "You are an engineer working specialized in industrial automation. If the question contains words in double brackets [[like this]], preserve them exactly as they appear. Please answer the following question in " + IdiomaDetectado + " and respond in the following JSON format: { \"Reply_text\": \"Your text here\" }."
};
}
private string CrearMensajeDeWebSearch()
{
return "You are a helpful assistant specialized in industrial automation and technical topics with web search capabilities. " +
"When users ask questions, you can use web search to find current and accurate information to provide comprehensive answers. " +
"If the question contains words in double brackets [[like this]], preserve them exactly as they appear in your response. " +
"Use the web search tool when you need to find recent information, current data, or specific technical details that might not be in your training data. " +
"Always provide well-researched, accurate answers and cite your sources when using web search results. " +
"Please write your answer in " + IdiomaDetectado + ". " +
"You can respond in JSON format like { \"Reply_text\": \"Your answer here\" } or provide a direct text response.";
}
private string CrearMensajeDeUsuario(string texto) =>
Opciones.Instance.modo switch
{
@ -322,6 +553,12 @@ namespace GTPCorrgir
Opciones.modoDeUso.Ortografia =>
$"Please check the following text for spelling errors and provide the corrected version. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"{texto}\"",
Opciones.modoDeUso.PreguntaRespuesta =>
texto, // Para pregunta-respuesta, enviamos el texto directamente como la pregunta
Opciones.modoDeUso.ClaudeWebSearch =>
texto, // Para web search, enviamos la pregunta directamente
Opciones.modoDeUso.Traducir_a_Ingles =>
$"Please check the following text for spelling errors and provide the corrected version tranlated to English. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"{texto}\"",
@ -331,6 +568,9 @@ namespace GTPCorrgir
Opciones.modoDeUso.Traducir_a_Espanol =>
$"Please check the following text for spelling errors and provide the corrected version tranlated to Spanish. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"{texto}\"",
Opciones.modoDeUso.Traducir_a_Portugues =>
$"Please check the following text for spelling errors and provide the corrected version tranlated to Portuguese. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"{texto}\"",
_ => texto
};
@ -440,6 +680,137 @@ namespace GTPCorrgir
}
}
private async Task<string> CallClaudeApi(string input)
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("x-api-key", _claudeApiKey);
_httpClient.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
var requestData = new
{
model = "claude-sonnet-4-20250514",
max_tokens = 4096,
temperature = 1,
system = CrearMensajeDeSistema(),
messages = new[]
{
new { role = "user", content = CrearMensajeDeUsuario(input) }
},
thinking = new
{
type = "enabled",
budget_tokens = 2048
}
};
return await EnviarSolicitudLLM("https://api.anthropic.com/v1/messages", requestData);
}
catch (Exception ex)
{
Log.Log($"Error en llamada a Claude API: {ex.Message}");
throw;
}
}
private async Task<string> CallClaudeWebSearchApi(string input)
{
var resultado = await CallClaudeWebSearchApiCompleto(input);
return resultado.texto;
}
private async Task<(string texto, string respuestaCompleta)> CallClaudeWebSearchApiCompleto(string input)
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("x-api-key", _claudeApiKey);
_httpClient.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
_httpClient.DefaultRequestHeaders.Add("anthropic-beta", "web-search-2025-03-05");
var requestData = new
{
model = "claude-sonnet-4-20250514",
max_tokens = 4166,
temperature = 1,
system = CrearMensajeDeWebSearch(),
messages = new[]
{
new { role = "user", content = input } // Para web search, enviamos la pregunta directamente
},
tools = new[]
{
new
{
name = "web_search",
type = "web_search_20250305"
}
},
thinking = new
{
type = "enabled",
budget_tokens = 4123
}
};
var content = new StringContent(
JsonConvert.SerializeObject(requestData),
Encoding.UTF8,
"application/json"
);
Log.Log($"Enviando solicitud a https://api.anthropic.com/v1/messages");
Log.Log($"Datos de solicitud: {JsonConvert.SerializeObject(requestData)}");
using var response = await _httpClient.PostAsync("https://api.anthropic.com/v1/messages", content);
var responseContent = await response.Content.ReadAsStringAsync();
// Para Claude Web Search, filtrar campos encrypted_content largos del log
string logContent = responseContent;
if (responseContent.Contains("encrypted_content") || responseContent.Contains("signature"))
{
logContent = FiltrarEncryptedContentParaLog(responseContent);
}
Log.Log($"Respuesta recibida: {logContent}");
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Error en la solicitud HTTP: {response.StatusCode} - {responseContent}"
);
}
var data = JsonConvert.DeserializeObject<dynamic>(responseContent);
if (data.content != null && data.content.Count > 0)
{
// Buscar el elemento con type = "text" en el array de content
foreach (var contentItem in data.content)
{
if (contentItem.type == "text")
{
return (contentItem.text.ToString(), responseContent);
}
}
// Si no encuentra un elemento con type="text", usar el primer elemento como fallback
if (data.content[0].text != null)
{
return (data.content[0].text.ToString(), responseContent);
}
}
throw new ApplicationException("No se encontró contenido en la respuesta de Claude Web Search");
}
catch (Exception ex)
{
Log.Log($"Error en llamada a Claude Web Search API: {ex.Message}");
throw;
}
}
private async Task<string> EnviarSolicitudLLM(string endpoint, object requestData)
{
try
@ -456,7 +827,15 @@ namespace GTPCorrgir
using var response = await _httpClient.PostAsync(endpoint, content);
var responseContent = await response.Content.ReadAsStringAsync();
Log.Log($"Respuesta recibida: {responseContent}");
// Para Claude Web Search, filtrar campos encrypted_content largos del log
string logContent = responseContent;
if (endpoint.Contains("anthropic") && (responseContent.Contains("encrypted_content") || responseContent.Contains("signature")))
{
logContent = FiltrarEncryptedContentParaLog(responseContent);
}
Log.Log($"Respuesta recibida: {logContent}");
if (!response.IsSuccessStatusCode)
{
@ -476,6 +855,26 @@ namespace GTPCorrgir
}
throw new ApplicationException("Formato de respuesta de Ollama inválido");
}
else if (endpoint.Contains("anthropic"))
{
if (data.content != null && data.content.Count > 0)
{
// Buscar el elemento con type = "text" en el array de content
foreach (var contentItem in data.content)
{
if (contentItem.type == "text")
{
return contentItem.text;
}
}
// Si no encuentra un elemento con type="text", usar el primer elemento como fallback
if (data.content[0].text != null)
{
return data.content[0].text;
}
}
throw new ApplicationException("No se encontró contenido en la respuesta de Claude");
}
else // OpenAI, Groq, Grok
{
if (data.choices != null && data.choices.Count > 0)
@ -492,6 +891,215 @@ namespace GTPCorrgir
}
}
private string FiltrarEncryptedContentParaLog(string responseContent)
{
try
{
// Usar regex para reemplazar campos encrypted_content largos con un placeholder
var regex = new System.Text.RegularExpressions.Regex(
@"""encrypted_content""\s*:\s*""[^""]{50,}""",
System.Text.RegularExpressions.RegexOptions.IgnoreCase
);
string filtered = regex.Replace(responseContent, @"""encrypted_content"": ""[CONTENT_FILTERED_FOR_LOG]""");
// También filtrar encrypted_index si es muy largo
var regexIndex = new System.Text.RegularExpressions.Regex(
@"""encrypted_index""\s*:\s*""[^""]{50,}""",
System.Text.RegularExpressions.RegexOptions.IgnoreCase
);
filtered = regexIndex.Replace(filtered, @"""encrypted_index"": ""[INDEX_FILTERED_FOR_LOG]""");
// Filtrar signature si es muy largo
var regexSignature = new System.Text.RegularExpressions.Regex(
@"""signature""\s*:\s*""[^""]{20,}""",
System.Text.RegularExpressions.RegexOptions.IgnoreCase
);
filtered = regexSignature.Replace(filtered, @"""signature"": ""[SIGNATURE_FILTERED_FOR_LOG]""");
return filtered;
}
catch (Exception ex)
{
Log.Log($"Error al filtrar encrypted_content: {ex.Message}");
// Si hay error al filtrar, devolver contenido original
return responseContent;
}
}
private bool EsSoloURL(string texto)
{
if (string.IsNullOrWhiteSpace(texto))
return false;
texto = texto.Trim();
// Lista de patrones comunes de URL
string[] patrones = {
"http://", "https://", "www.", "ftp://", "ftps://",
".com", ".net", ".org", ".edu", ".gov", ".mil",
".es", ".mx", ".ar", ".cl", ".co", ".pe"
};
return patrones.Any(patron => texto.ToLower().Contains(patron));
}
/// <summary>
/// Método de fallback que usa Grok para explicar errores cuando el procesamiento principal falla
/// </summary>
/// <param name="originalText">Texto original que se intentó procesar</param>
/// <param name="errorLogs">Logs de la interacción que falló</param>
/// <returns>Texto original + explicación del error, o solo el error si Grok también falla</returns>
public async Task<string> ExplicarErrorConGrok(string originalText, string errorLogs)
{
try
{
Log.Log("Iniciando fallback con Grok para explicar el error");
// Verificar que tenemos la API key de Grok
if (string.IsNullOrEmpty(_grokApiKey))
{
Log.Log("Error fallback: No hay API key de Grok disponible");
return GetLastErrorMessage(errorLogs);
}
// Preparar el prompt para Grok
string systemPrompt = "Eres un asistente técnico especializado en explicar errores de software. " +
"Analiza los logs de error proporcionados y explica de manera simple y clara qué salió mal y por qué, " +
"en español. Sé conciso y útil para el usuario final.";
string userPrompt = $"¿Podrías explicar qué no ha funcionado y por qué según estos logs?\n\n{errorLogs}";
// Llamar a Grok específicamente para explicar el error
string explicacion = await CallGrokForErrorExplanation(systemPrompt, userPrompt);
if (!string.IsNullOrEmpty(explicacion))
{
Log.Log("Grok proporcionó explicación del error exitosamente");
return $"{originalText}\n\nERROR: {explicacion}";
}
else
{
Log.Log("Grok no pudo proporcionar explicación del error");
return GetLastErrorMessage(errorLogs);
}
}
catch (Exception ex)
{
Log.Log($"Error en fallback de explicación con Grok: {ex.Message}");
return GetLastErrorMessage(errorLogs);
}
}
/// <summary>
/// Método específico para llamar a Grok para explicaciones de errores
/// </summary>
private async Task<string> CallGrokForErrorExplanation(string systemPrompt, string userPrompt)
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_grokApiKey}");
var requestData = new
{
messages = new[]
{
new { role = "system", content = systemPrompt },
new { role = "user", content = userPrompt }
},
model = "grok-beta",
stream = false,
temperature = 0.3, // Baja temperatura para respuestas más consistentes en explicaciones de errores
max_tokens = 500 // Limitar tokens para explicaciones concisas
};
var content = new StringContent(
JsonConvert.SerializeObject(requestData),
Encoding.UTF8,
"application/json"
);
Log.Log("Enviando solicitud de explicación de error a Grok");
using var response = await _httpClient.PostAsync("https://api.x.ai/v1/chat/completions", content);
var responseContent = await response.Content.ReadAsStringAsync();
Log.Log($"Respuesta de Grok para explicación de error: {responseContent}");
if (!response.IsSuccessStatusCode)
{
Log.Log($"Error en solicitud a Grok para explicación: {response.StatusCode} - {responseContent}");
return null;
}
var data = JsonConvert.DeserializeObject<dynamic>(responseContent);
if (data?.choices != null && data.choices.Count > 0)
{
string explanation = data.choices[0].message.content?.ToString();
return explanation?.Trim();
}
Log.Log("No se encontró contenido en la respuesta de Grok para explicación de error");
return null;
}
catch (Exception ex)
{
Log.Log($"Error en llamada a Grok para explicación de error: {ex.Message}");
return null;
}
}
/// <summary>
/// Extrae el último mensaje de error de los logs
/// </summary>
private string GetLastErrorMessage(string errorLogs)
{
try
{
if (string.IsNullOrEmpty(errorLogs))
return "Error desconocido";
// Buscar la última línea que contenga "Error no controlado" o "Error"
var lines = errorLogs.Split('\n');
for (int i = lines.Length - 1; i >= 0; i--)
{
if (lines[i].Contains("Error no controlado:") ||
lines[i].Contains("Error durante la corrección:") ||
lines[i].Contains("Error en CorregirTexto:"))
{
// Extraer solo la parte del error después de los dos puntos
var parts = lines[i].Split(':', 2);
if (parts.Length > 1)
{
return parts[1].Trim();
}
return lines[i].Trim();
}
}
// Si no encuentra un error específico, devolver la última línea no vacía
for (int i = lines.Length - 1; i >= 0; i--)
{
if (!string.IsNullOrWhiteSpace(lines[i]))
{
return lines[i].Trim();
}
}
return "Error desconocido";
}
catch (Exception ex)
{
Log.Log($"Error al extraer último mensaje de error: {ex.Message}");
return "Error desconocido";
}
}
public void Dispose()
{
Dispose(true);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
paddleocr/det/inference/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,132 @@
# Script para descargar e instalar modelos PaddleOCR
# Guardar como: download-paddleocr-models.ps1
param(
[string]$OutputDirectory = ".\paddleocr",
[switch]$ForceDownload = $false
)
# URLs de modelos PaddleOCR (inglés por defecto)
$modelUrls = @{
"det" = "https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_det_slim_infer.tar"
"rec" = "https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_rec_slim_infer.tar"
"cls" = "https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_slim_infer.tar"
"keys" = "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.6/ppocr/utils/en_dict.txt"
}
# Crear directorios si no existen
function EnsureDirectory($path) {
if (-not (Test-Path $path)) {
New-Item -Path $path -ItemType Directory | Out-Null
Write-Host "Creado directorio: $path" -ForegroundColor Green
}
}
# Descargar archivo con barra de progreso
function DownloadFile($url, $outputFile) {
$webClient = New-Object System.Net.WebClient
$webClient.Encoding = [System.Text.Encoding]::UTF8
$downloadExists = Test-Path $outputFile
if ($downloadExists -and -not $ForceDownload) {
Write-Host "El archivo ya existe: $outputFile. Use -ForceDownload para sobrescribir." -ForegroundColor Yellow
return $false
}
Write-Host "Descargando $url..." -ForegroundColor Cyan
try {
$webClient.DownloadFile($url, $outputFile)
Write-Host "Descarga completada: $outputFile" -ForegroundColor Green
return $true
} catch {
Write-Host "Error al descargar $url : $_" -ForegroundColor Red
return $false
}
}
# Extraer archivo TAR
function ExtractTarFile($tarFile, $destDir) {
Write-Host "Extrayendo $tarFile..." -ForegroundColor Cyan
# Verificar si tar está disponible
$tarAvailable = $null -ne (Get-Command "tar" -ErrorAction SilentlyContinue)
if ($tarAvailable) {
# Usar tar nativo
try {
tar -xf $tarFile -C $destDir
Write-Host "Extracción completada: $tarFile" -ForegroundColor Green
return $true
} catch {
Write-Host "Error al extraer con tar: $_" -ForegroundColor Red
}
}
# Fallback a .NET
try {
Add-Type -AssemblyName System.IO.Compression.FileSystem
# Descomprimir TAR con .NET
# Nota: Esto es un ejemplo simplificado. Necesitarías una biblioteca específica para TAR
Write-Host "TAR nativo no disponible. Se requiere una biblioteca para manejar archivos TAR." -ForegroundColor Yellow
# Aquí puedes agregar código para usar SharpCompress u otra biblioteca para TAR
# Por ejemplo:
# [Reflection.Assembly]::LoadFrom("path\to\SharpCompress.dll")
# $reader = [SharpCompress.Readers.ReaderFactory]::Open($tarFile)
# ...
return $false
} catch {
Write-Host "Error al extraer $tarFile : $_" -ForegroundColor Red
return $false
}
}
# Crear directorio principal
EnsureDirectory $OutputDirectory
# Crear estructura de directorios
$modelTypes = @("det", "rec", "cls", "keys")
foreach ($type in $modelTypes) {
$typeDir = Join-Path $OutputDirectory $type
EnsureDirectory $typeDir
if ($type -ne "keys") {
$inferenceDir = Join-Path $typeDir "inference"
EnsureDirectory $inferenceDir
}
}
# Descargar y procesar cada modelo
foreach ($entry in $modelUrls.GetEnumerator()) {
$type = $entry.Key
$url = $entry.Value
$typeDir = Join-Path $OutputDirectory $type
if ($type -eq "keys") {
# Para el archivo de claves, solo descargarlo directamente
$outputFile = Join-Path $typeDir "ppocr_keys.txt"
DownloadFile $url $outputFile
} else {
# Para los modelos, descargar TAR y extraerlo
$tarFile = Join-Path $env:TEMP "$type.tar"
$downloaded = DownloadFile $url $tarFile
if ($downloaded) {
$extracted = ExtractTarFile $tarFile $typeDir
# Limpiar archivo temporal
if (Test-Path $tarFile) {
Remove-Item $tarFile -Force
}
}
}
}
Write-Host "`nInstalación de modelos PaddleOCR completada en: $OutputDirectory" -ForegroundColor Green
Write-Host "Para usar estos modelos, configura PaddleOCREngine con las siguientes rutas:" -ForegroundColor Yellow
Write-Host " - det_infer: $OutputDirectory\det\inference" -ForegroundColor White
Write-Host " - rec_infer: $OutputDirectory\rec\inference" -ForegroundColor White
Write-Host " - cls_infer: $OutputDirectory\cls\inference" -ForegroundColor White
Write-Host " - keys: $OutputDirectory\keys\ppocr_keys.txt" -ForegroundColor White

View File

@ -0,0 +1,95 @@
0
1
2
3
4
5
6
7
8
9
:
;
<
=
>
?
@
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
[
\
]
^
_
`
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
{
|
}
~
!
"
#
$
%
&
'
(
)
*
+
,
-
.
/

Binary file not shown.

Binary file not shown.

Binary file not shown.