Agregado de opcion de configuracion.

This commit is contained in:
Miguel 2024-11-14 16:37:08 +01:00
parent 2caf2b96f5
commit c047fb2c01
11 changed files with 1474 additions and 476 deletions

View File

@ -1,13 +1,12 @@
<Window <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:md="clr-namespace:Markdown.Xaml;assembly=Markdown.Xaml" xmlns:md="clr-namespace:Markdown.Xaml;assembly=Markdown.Xaml"
xmlns:av="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="av" x:Class="GTPCorrgir.Chat" xmlns:av="http://schemas.microsoft.com/expression/blend/2008"
Title="Chat with OpenAI" Height="300" Width="300" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="av"
ResizeMode="CanResizeWithGrip" WindowStyle="None" x:Class="GTPCorrgir.Chat" Title="Chat with OpenAI" Height="300" Width="300" ResizeMode="CanResizeWithGrip"
Background="Transparent" AllowsTransparency="True" WindowStyle="None" Background="Transparent" AllowsTransparency="True" MouseEnter="Window_MouseEnter"
MouseEnter="Window_MouseEnter" MouseLeave="Window_MouseLeave" KeyDown="Window_KeyDown" MouseLeave="Window_MouseLeave" KeyDown="Window_KeyDown" Opacity="0.8" av:DesignHeight="320.439"
Opacity="0.8" av:DesignHeight="320.439" av:DesignWidth="609.769"> av:DesignWidth="609.769">
<Window.Resources> <Window.Resources>
<md:Markdown x:Key="MarkdownConverter" /> <md:Markdown x:Key="MarkdownConverter" />
</Window.Resources> </Window.Resources>
@ -15,48 +14,49 @@
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<!-- Model Selector con altura automática -->
<RowDefinition Height="2*" /> <RowDefinition Height="2*" />
<!-- Área de Respuesta con 2/3 del espacio disponible -->
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<!-- Área de Pregunta con 1/3 del espacio disponible -->
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Selector del Modelo --> <!-- Barra superior con controles -->
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Selector de modelo LLM --> <!-- Selector de modelo LLM -->
<ComboBox x:Name="modelSelector" Grid.Column="0" Margin="1" SelectionChanged="CambiarModelo" /> <ComboBox x:Name="modelSelector" Grid.Column="0" Margin="1" SelectionChanged="CambiarModelo" />
<!-- Botón de configuración -->
<Button x:Name="settingsButton" Grid.Column="1" Content="⚙" Width="20" Height="20" Margin="5,0"
Click="SettingsButton_Click" ToolTip="Configuración" />
<!-- Área para mover la ventana --> <!-- Área para mover la ventana -->
<Border Background="#444" Height="20" Width="20" Grid.Column="1" Margin="10,0,10,0" <Border Background="#444" Height="20" Width="20" Grid.Column="2" Margin="5,0,10,0"
MouseLeftButtonDown="Border_MouseLeftButtonDown" Cursor="SizeAll" ToolTip="Mover ventana"> MouseLeftButtonDown="Border_MouseLeftButtonDown" Cursor="SizeAll" ToolTip="Mover ventana">
<TextBlock Text="&#x2630;" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" /> <TextBlock Text="&#x2630;" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" />
</Border> </Border>
</Grid> </Grid>
<!-- Área de Respuesta --> <!-- Área de Respuesta -->
<Grid Grid.Row="1" Margin="1"> <Grid Grid.Row="1" Margin="1">
<RichTextBox Name="responseArea" IsReadOnly="True" Grid.Row="1"> <RichTextBox Name="responseArea" IsReadOnly="True">
<RichTextBox.Resources> <RichTextBox.Resources>
<md:Markdown x:Key="Markdown" /> <md:Markdown x:Key="Markdown" />
</RichTextBox.Resources> </RichTextBox.Resources>
</RichTextBox> </RichTextBox>
<Button x:Name="clearButton" Content="Limpiar" Width="40" Height="24" <Button x:Name="clearButton" Content="Limpiar" Width="40" Height="24" HorizontalAlignment="Right"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0" Click="clearButton_Click" /> VerticalAlignment="Bottom" Margin="0" Click="clearButton_Click" />
</Grid> </Grid>
<!-- Área de Pregunta con Botón Superpuesto --> <!-- Área de Pregunta con Botón -->
<Grid Grid.Row="2" Margin="1"> <Grid Grid.Row="2" Margin="1">
<TextBox x:Name="questionArea" Padding="10" <TextBox x:Name="questionArea" Padding="10" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" Background="White" PreviewKeyDown="QuestionArea_PreviewKeyDown"/> Background="White" PreviewKeyDown="QuestionArea_PreviewKeyDown" />
<Button x:Name="sendButton" Content="Enviar" Width="40" Height="24" <Button x:Name="sendButton" Content="Enviar" Width="40" Height="24" HorizontalAlignment="Right"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0" Click="SendButton_Click"/> VerticalAlignment="Bottom" Margin="0" Click="SendButton_Click" />
</Grid> </Grid>
</Grid> </Grid>
</Window> </Window>

View File

@ -15,37 +15,69 @@ using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using static GTPCorrgir.Opciones; using static GTPCorrgir.Opciones;
using static System.Net.WebRequestMethods; using static System.Net.WebRequestMethods;
using Brushes = System.Windows.Media.Brushes;
using Color = System.Windows.Media.Color;
using ComboBox = System.Windows.Controls.ComboBox; using ComboBox = System.Windows.Controls.ComboBox;
using Cursors = System.Windows.Input.Cursors; using Cursors = System.Windows.Input.Cursors;
using FontFamily = System.Windows.Media.FontFamily;
using KeyEventArgs = System.Windows.Input.KeyEventArgs; using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using MouseEventArgs = System.Windows.Input.MouseEventArgs; using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using Timer = System.Windows.Forms.Timer;
namespace GTPCorrgir namespace GTPCorrgir
{ {
/// <summary>
/// Interaction logic for Chat.xaml
/// </summary>
public partial class Chat : Window public partial class Chat : Window
{ {
gtpask AI_API; gtpask AI_API;
string respuestas; string respuestas;
private Timer opacityTimer;
private bool isMouseOver = false;
private const double OPACITY_ACTIVE = 1.0;
private const double OPACITY_INACTIVE = 0.2;
private const int OPACITY_DELAY_MS = 3000; // 10 segundos
public Chat(gtpask GTP) public Chat(gtpask GTP)
{ {
InitializeComponent(); InitializeComponent();
InitializeOpacityTimer();
PositionWindow(); PositionWindow();
// Inicializar componentes de la UI, por ejemplo, llenar el ComboBox
// Inicializar componentes de la UI
AI_API = GTP; AI_API = GTP;
questionArea.Text = ""; //GTP.TextoACorregir; questionArea.Text = "";
respuestas = ""; respuestas = "";
this.Opacity = OPACITY_ACTIVE;
InitializeModelSelector();
}
private void InitializeOpacityTimer()
{
opacityTimer = new Timer();
opacityTimer.Interval = OPACITY_DELAY_MS;
opacityTimer.Tick += (s, e) =>
{
if (!isMouseOver)
{
this.Dispatcher.Invoke(() =>
{
this.Opacity = OPACITY_INACTIVE;
});
}
opacityTimer.Stop();
};
}
private void InitializeModelSelector()
{
foreach (KeyValuePair<LLM_a_Usar, string> kvp in Opciones.Instance.nombreLLM) foreach (KeyValuePair<LLM_a_Usar, string> kvp in Opciones.Instance.nombreLLM)
{ {
ComboBoxItem item = new ComboBoxItem(); ComboBoxItem item = new ComboBoxItem();
item.Content = kvp.Value; // El texto que se mostrará item.Content = kvp.Value;
item.Tag = kvp.Key; // Guarda el valor enum en el Tag para acceso posterior item.Tag = kvp.Key;
modelSelector.Items.Add(item); modelSelector.Items.Add(item);
// Verifica si este ítem debe ser el seleccionado
if (kvp.Key == Opciones.Instance.LLM) if (kvp.Key == Opciones.Instance.LLM)
{ {
modelSelector.SelectedItem = item; modelSelector.SelectedItem = item;
@ -53,20 +85,81 @@ namespace GTPCorrgir
} }
} }
private void SettingsButton_Click(object sender, RoutedEventArgs e)
{
var settingsWindow = new SettingsWindow
{
Owner = this,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
if (settingsWindow.ShowDialog() == true)
{
// Aplicar cambios de configuración
ApplySettings();
}
}
private void ApplySettings()
{
var settings = UserSettings.Instance;
// Aplicar opacidad
opacityTimer.Stop();
opacityTimer.Interval = ((int)TimeSpan.FromMilliseconds(settings.Appearance.OpacityDelay).TotalMilliseconds);
// Aplicar fuente
questionArea.FontFamily = new FontFamily(settings.Appearance.FontFamily);
questionArea.FontSize = settings.Appearance.FontSize;
responseArea.FontFamily = new FontFamily(settings.Appearance.FontFamily);
responseArea.FontSize = settings.Appearance.FontSize;
// Aplicar tema
if (settings.Appearance.Theme == "Dark")
{
// Aplicar tema oscuro
this.Background = new SolidColorBrush(Color.FromArgb(200, 30, 30, 30));
responseArea.Background = new SolidColorBrush(Color.FromArgb(255, 40, 40, 40));
responseArea.Foreground = Brushes.White;
questionArea.Background = new SolidColorBrush(Color.FromArgb(255, 50, 50, 50));
questionArea.Foreground = Brushes.White;
}
else
{
// Aplicar tema claro
this.Background = new SolidColorBrush(Color.FromArgb(200, 240, 240, 240));
responseArea.Background = Brushes.White;
responseArea.Foreground = Brushes.Black;
questionArea.Background = Brushes.White;
questionArea.Foreground = Brushes.Black;
}
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
ApplySettings(); // Aplicar configuración inicial
}
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{ {
// Iniciar movimiento de la ventana si se presiona el botón izquierdo del ratón
if (e.LeftButton == MouseButtonState.Pressed) if (e.LeftButton == MouseButtonState.Pressed)
{ {
this.DragMove(); this.DragMove();
} }
} }
private void Window_MouseEnter(object sender, MouseEventArgs e) private void Window_MouseEnter(object sender, MouseEventArgs e)
{ {
// Hacer la ventana opaca cuando el ratón esté sobre ella isMouseOver = true;
this.Opacity = 1.0; opacityTimer.Stop();
this.Opacity = OPACITY_ACTIVE;
}
private void Window_MouseLeave(object sender, MouseEventArgs e)
{
isMouseOver = false;
opacityTimer.Start();
} }
private void Window_KeyDown(object sender, KeyEventArgs e) private void Window_KeyDown(object sender, KeyEventArgs e)
@ -82,27 +175,21 @@ namespace GTPCorrgir
if (e.Key == Key.Enter && !e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Shift)) if (e.Key == Key.Enter && !e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Shift))
{ {
SendButton_Click(this, new RoutedEventArgs()); SendButton_Click(this, new RoutedEventArgs());
e.Handled = true; // Prevenir el salto de línea en el TextBox e.Handled = true;
} }
} }
private void Window_MouseLeave(object sender, MouseEventArgs e) private async void SendButton_Click(object sender, RoutedEventArgs e)
{ {
// Hacer la ventana transparente cuando el ratón no esté sobre ella
this.Opacity = 0.2; // Ajusta este valor a tu preferencia
}
private void SendButton_Click(object sender, RoutedEventArgs e)
{
// Aquí lógica para enviar pregunta y recibir respuesta de OpenAI
AI_API.TextoACorregir = questionArea.Text; AI_API.TextoACorregir = questionArea.Text;
if (AI_API.TextoACorregir.Length > 3) if (AI_API.TextoACorregir.Length > 3)
{ {
sendButton.IsEnabled = false; // Deshabilitar el botón de envío try
Mouse.OverrideCursor = Cursors.Wait; // Cambiar el cursor a espera {
sendButton.IsEnabled = false;
Mouse.OverrideCursor = Cursors.Wait;
Task.Run(async () => await Task.Run(async () =>
{ {
try try
{ {
@ -110,59 +197,49 @@ namespace GTPCorrgir
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("Error durante la corrección de texto: " + ex.Message); Console.WriteLine($"Error durante la corrección de texto: {ex.Message}");
throw;
} }
finally });
{
Dispatcher.Invoke(async () => // Nota el 'async' aquí para permitir 'await'
{
if (AI_API.TextoCorregido != null) if (AI_API.TextoCorregido != null)
{ {
System.Windows.Clipboard.SetText(AI_API.TextoCorregido); System.Windows.Clipboard.SetText(AI_API.TextoCorregido);
//responseArea. .Text += AI_API.TextoCorregido + "\r\n";
AddMarkdownContent(AI_API.TextoCorregido + "\r\n"); AddMarkdownContent(AI_API.TextoCorregido + "\r\n");
Mouse.OverrideCursor = null; // Restaurar el cursor normal
sendButton.IsEnabled = true; // Habilitar el botón de envío
} }
});
} }
}); catch (Exception ex)
{
AddMarkdownContent($"Error: {ex.Message}\r\n");
}
finally
{
Mouse.OverrideCursor = null;
sendButton.IsEnabled = true;
}
} }
} }
public void AddMarkdownContent(string markdownText) public void AddMarkdownContent(string markdownText)
{ {
// Transforma el texto Markdown a un FlowDocument
var markdown = new Markdown.Xaml.Markdown(); var markdown = new Markdown.Xaml.Markdown();
respuestas += markdownText + "\r\n"; respuestas += markdownText + "\r\n";
responseArea.Document = markdown.Transform(respuestas); responseArea.Document = markdown.Transform(respuestas);
} }
private void PositionWindow() private void PositionWindow()
{ {
// Obtener la posición del cursor
var cursorPosition = System.Windows.Forms.Cursor.Position; var cursorPosition = System.Windows.Forms.Cursor.Position;
// Determinar en qué pantalla está el cursor
var screen = Screen.FromPoint(cursorPosition); var screen = Screen.FromPoint(cursorPosition);
// Calcular la ubicación central en la pantalla actual
this.Left = (screen.WorkingArea.Width - this.Width) / 2 + screen.WorkingArea.Left; this.Left = (screen.WorkingArea.Width - this.Width) / 2 + screen.WorkingArea.Left;
this.Top = (screen.WorkingArea.Height - this.Height) / 2 + screen.WorkingArea.Top; this.Top = (screen.WorkingArea.Height - this.Height) / 2 + screen.WorkingArea.Top;
} }
private void CambiarModelo(object sender, SelectionChangedEventArgs e) private void CambiarModelo(object sender, SelectionChangedEventArgs e)
{ {
ComboBox comboBox = sender as ComboBox; if (sender is ComboBox comboBox && comboBox.SelectedItem is ComboBoxItem selectedItem)
ComboBoxItem selectedItem = comboBox.SelectedItem as ComboBoxItem;
if (selectedItem != null)
{ {
LLM_a_Usar selectedEnum = (LLM_a_Usar)selectedItem.Tag; Opciones.Instance.LLM = (LLM_a_Usar)selectedItem.Tag;
Opciones.Instance.LLM = selectedEnum; // Suponiendo que hay una propiedad para establecerlo
} }
} }
@ -171,5 +248,11 @@ namespace GTPCorrgir
respuestas = ""; respuestas = "";
AddMarkdownContent(""); AddMarkdownContent("");
} }
protected override void OnClosed(EventArgs e)
{
opacityTimer?.Dispose();
base.OnClosed(e);
}
} }
} }

115
ChatHistory.cs Normal file
View File

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace GTPCorrgir
{
public class ChatMessage
{
public string Content { get; set; }
public DateTime Timestamp { get; set; }
public bool IsUser { get; set; }
public string ModelUsed { get; set; }
}
public class ChatHistory
{
private const string HISTORY_FOLDER = "ChatHistory";
private const int MAX_HISTORY_FILES = 10;
private readonly string historyPath;
private List<ChatMessage> currentSession;
public ChatHistory()
{
historyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, HISTORY_FOLDER);
currentSession = new List<ChatMessage>();
EnsureHistoryFolderExists();
CleanupOldHistories();
}
private void EnsureHistoryFolderExists()
{
if (!Directory.Exists(historyPath))
{
Directory.CreateDirectory(historyPath);
}
}
private void CleanupOldHistories()
{
var files = Directory.GetFiles(historyPath, "*.json")
.OrderByDescending(f => File.GetLastWriteTime(f));
int count = 0;
foreach (var file in files)
{
count++;
if (count > MAX_HISTORY_FILES)
{
try
{
File.Delete(file);
}
catch (Exception ex)
{
Console.WriteLine($"Error deleting old history: {ex.Message}");
}
}
}
}
public void AddMessage(string content, bool isUser, string modelUsed)
{
var message = new ChatMessage
{
Content = content,
Timestamp = DateTime.Now,
IsUser = isUser,
ModelUsed = modelUsed
};
currentSession.Add(message);
}
public void SaveSession()
{
if (currentSession.Any())
{
string filename = $"chat_history_{DateTime.Now:yyyyMMdd_HHmmss}.json";
string fullPath = Path.Combine(historyPath, filename);
try
{
string json = JsonConvert.SerializeObject(currentSession, Formatting.Indented);
File.WriteAllText(fullPath, json);
}
catch (Exception ex)
{
Console.WriteLine($"Error saving chat history: {ex.Message}");
}
}
}
public List<ChatMessage> LoadLastSession()
{
try
{
var files = Directory.GetFiles(historyPath, "*.json")
.OrderByDescending(f => File.GetLastWriteTime(f))
.FirstOrDefault();
if (files != null)
{
string json = File.ReadAllText(files);
return JsonConvert.DeserializeObject<List<ChatMessage>>(json);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading chat history: {ex.Message}");
}
return new List<ChatMessage>();
}
}
}

123
ErrorLogger.cs Normal file
View File

@ -0,0 +1,123 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace GTPCorrgir
{
public class ErrorLogger
{
private const string ERROR_FOLDER = "ErrorLogs";
private const int MAX_LOG_FILES = 5;
private readonly string logPath;
private readonly object lockObj = new object();
public ErrorLogger()
{
logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ERROR_FOLDER);
EnsureLogFolderExists();
CleanupOldLogs();
}
private void EnsureLogFolderExists()
{
if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath);
}
}
private void CleanupOldLogs()
{
try
{
var files = Directory.GetFiles(logPath, "*.json")
.OrderByDescending(f => File.GetLastWriteTime(f));
int count = 0;
foreach (var file in files)
{
count++;
if (count > MAX_LOG_FILES)
{
File.Delete(file);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error cleaning up logs: {ex.Message}");
}
}
public async Task LogErrorAsync(Exception ex, string context = "")
{
var errorInfo = new
{
Timestamp = DateTime.Now,
Context = context,
Exception = new
{
Message = ex.Message,
StackTrace = ex.StackTrace,
Source = ex.Source,
TargetSite = ex.TargetSite?.ToString(),
InnerException = ex.InnerException?.Message
},
SystemInfo = new
{
OSVersion = Environment.OSVersion.ToString(),
MachineName = Environment.MachineName,
ProcessorCount = Environment.ProcessorCount,
WorkingSet = Environment.WorkingSet,
CurrentDirectory = Environment.CurrentDirectory
}
};
string filename = $"error_log_{DateTime.Now:yyyyMMdd_HHmmss}.json";
string fullPath = Path.Combine(logPath, filename);
try
{
string json = JsonConvert.SerializeObject(errorInfo, Formatting.Indented);
await File.WriteAllTextAsync(fullPath, json);
// También escribir al log de debug
Debug.WriteLine($"Error logged: {context} - {ex.Message}");
}
catch (Exception logEx)
{
Debug.WriteLine($"Error writing to error log: {logEx.Message}");
}
}
public async Task<string> GetErrorSummaryAsync()
{
var summary = new StringBuilder();
try
{
var files = Directory.GetFiles(logPath, "*.json")
.OrderByDescending(f => File.GetLastWriteTime(f))
.Take(5);
foreach (var file in files)
{
string content = await File.ReadAllTextAsync(file);
var error = JsonConvert.DeserializeObject<dynamic>(content);
summary.AppendLine($"Time: {error.Timestamp}");
summary.AppendLine($"Context: {error.Context}");
summary.AppendLine($"Error: {error.Exception.Message}");
summary.AppendLine("-------------------");
}
}
catch (Exception ex)
{
summary.AppendLine($"Error reading error logs: {ex.Message}");
}
return summary.ToString();
}
}
}

83
SettingsWindow.xaml Normal file
View File

@ -0,0 +1,83 @@
<Window x:Class="GTPCorrgir.SettingsWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Configuración" Height="450" Width="400"
WindowStartupLocation="CenterOwner">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl Grid.Row="1" Margin="0,10">
<TabItem Header="Apariencia">
<StackPanel Margin="10">
<GroupBox Header="Tema" Margin="0,0,0,10">
<StackPanel Margin="5">
<RadioButton x:Name="LightTheme" Content="Claro" Margin="0,5" />
<RadioButton x:Name="DarkTheme" Content="Oscuro" Margin="0,5" />
</StackPanel>
</GroupBox>
<GroupBox Header="Fuente" Margin="0,0,0,10">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Tamaño:" Margin="0,5" />
<ComboBox x:Name="FontSizeCombo" Grid.Column="1" Margin="5" />
<TextBlock Text="Familia:" Grid.Row="1" Margin="0,5" />
<ComboBox x:Name="FontFamilyCombo" Grid.Row="1" Grid.Column="1" Margin="5" />
</Grid>
</GroupBox>
<GroupBox Header="Opacidad" Margin="0,0,0,10">
<StackPanel Margin="5">
<TextBlock Text="Retraso antes de transparencia (segundos):" />
<Slider x:Name="OpacityDelaySlider" Minimum="1" Maximum="30" Value="10" TickFrequency="1"
IsSnapToTickEnabled="True" />
<TextBlock Text="{Binding ElementName=OpacityDelaySlider, Path=Value, StringFormat={}{0} s}" />
</StackPanel>
</GroupBox>
</StackPanel>
</TabItem>
<TabItem Header="Comportamiento">
<StackPanel Margin="10">
<CheckBox x:Name="AutoSaveCheckbox" Content="Guardar historial automáticamente" Margin="0,5" />
<CheckBox x:Name="ShowNotificationsCheckbox" Content="Mostrar notificaciones" Margin="0,5" />
<CheckBox x:Name="AutoCopyCheckbox" Content="Copiar respuestas al portapapeles" Margin="0,5" />
<GroupBox Header="Notificaciones" Margin="0,10,0,0">
<StackPanel Margin="5">
<TextBlock Text="Duración de notificaciones (segundos):" />
<Slider x:Name="NotificationDurationSlider" Minimum="1" Maximum="10" Value="5"
TickFrequency="1" IsSnapToTickEnabled="True" />
<TextBlock
Text="{Binding ElementName=NotificationDurationSlider, Path=Value, StringFormat={}{0} s}" />
</StackPanel>
</GroupBox>
</StackPanel>
</TabItem>
<TabItem Header="Avanzado">
<StackPanel Margin="10">
<Button Content="Ver registros de error" Click="ViewErrorLogs_Click" Margin="0,5" />
<Button Content="Borrar historial" Click="ClearHistory_Click" Margin="0,5" />
<Button Content="Restablecer configuración" Click="ResetSettings_Click" Margin="0,5" />
</StackPanel>
</TabItem>
</TabControl>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Guardar" Width="80" Click="SaveButton_Click" Margin="0,0,10,0" />
<Button Content="Cancelar" Width="80" Click="CancelButton_Click" />
</StackPanel>
</Grid>
</Window>

222
SettingsWindow.xaml.cs Normal file
View File

@ -0,0 +1,222 @@
using System;
using System.Windows;
using System.Windows.Media;
using System.Linq;
using System.Windows.Controls;
using MessageBox = System.Windows.MessageBox;
using TextBox = System.Windows.Controls.TextBox;
using Button = System.Windows.Controls.Button;
namespace GTPCorrgir
{
public partial class SettingsWindow : Window
{
private readonly UserSettings settings;
private readonly ErrorLogger errorLogger;
private readonly ChatHistory chatHistory;
public SettingsWindow()
{
InitializeComponent();
settings = UserSettings.Instance;
errorLogger = new ErrorLogger();
chatHistory = new ChatHistory();
LoadSettings();
InitializeFontControls();
}
private void InitializeFontControls()
{
// Cargar tamaños de fuente comunes
FontSizeCombo.ItemsSource = new[] { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24 };
FontSizeCombo.SelectedItem = settings.Appearance.FontSize;
// Cargar fuentes del sistema
var fontFamilies = Fonts.SystemFontFamilies
.OrderBy(f => f.Source)
.Select(f => f.Source);
FontFamilyCombo.ItemsSource = fontFamilies;
FontFamilyCombo.SelectedItem = settings.Appearance.FontFamily;
}
private void LoadSettings()
{
// Tema
(settings.Appearance.Theme == "Light" ? LightTheme : DarkTheme).IsChecked = true;
// Opacidad
OpacityDelaySlider.Value = settings.Appearance.OpacityDelay / 1000.0;
// Comportamiento
AutoSaveCheckbox.IsChecked = settings.Behavior.AutoSaveHistory;
ShowNotificationsCheckbox.IsChecked = settings.Behavior.ShowNotifications;
AutoCopyCheckbox.IsChecked = settings.Behavior.AutoCopyToClipboard;
NotificationDurationSlider.Value = settings.Behavior.NotificationDuration / 1000.0;
}
private async void ViewErrorLogs_Click(object sender, RoutedEventArgs e)
{
try
{
string summary = await errorLogger.GetErrorSummaryAsync();
ShowScrollableDialog("Registros de Error", summary);
}
catch (Exception ex)
{
MessageBox.Show($"Error al cargar los registros: {ex.Message}",
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ShowScrollableDialog(string title, string content)
{
var dialog = new Window
{
Title = title,
Width = 600,
Height = 400,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = this,
ResizeMode = ResizeMode.CanResize,
ShowInTaskbar = false
};
var textBox = new TextBox
{
Text = content,
IsReadOnly = true,
TextWrapping = TextWrapping.Wrap,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
Margin = new Thickness(10),
AcceptsReturn = true
};
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
var closeButton = new Button
{
Content = "Cerrar",
Width = 80,
Height = 25,
Margin = new Thickness(10),
HorizontalAlignment = System.Windows.HorizontalAlignment.Right
};
closeButton.Click += (s, e) => dialog.Close();
Grid.SetRow(textBox, 0);
Grid.SetRow(closeButton, 1);
grid.Children.Add(textBox);
grid.Children.Add(closeButton);
dialog.Content = grid;
dialog.ShowDialog();
}
private async void ClearHistory_Click(object sender, RoutedEventArgs e)
{
var result = MessageBox.Show(
"¿Está seguro de que desea borrar todo el historial de chat?\nEsta acción no se puede deshacer.",
"Confirmar borrado",
MessageBoxButton.YesNo,
MessageBoxImage.Warning
);
if (result == MessageBoxResult.Yes)
{
try
{
// Aquí implementarías la lógica para borrar el historial
// Por ejemplo: await chatHistory.ClearAllAsync();
MessageBox.Show(
"El historial ha sido borrado correctamente.",
"Éxito",
MessageBoxButton.OK,
MessageBoxImage.Information
);
}
catch (Exception ex)
{
await errorLogger.LogErrorAsync(ex, "Error al borrar historial");
MessageBox.Show(
$"Error al borrar el historial: {ex.Message}",
"Error",
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
}
}
private void ResetSettings_Click(object sender, RoutedEventArgs e)
{
var result = MessageBox.Show(
"¿Está seguro de que desea restablecer toda la configuración a sus valores predeterminados?",
"Confirmar restablecimiento",
MessageBoxButton.YesNo,
MessageBoxImage.Warning
);
if (result == MessageBoxResult.Yes)
{
settings.ResetToDefaults();
LoadSettings();
MessageBox.Show(
"La configuración ha sido restablecida correctamente.",
"Éxito",
MessageBoxButton.OK,
MessageBoxImage.Information
);
}
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
try
{
// Guardar tema
settings.Appearance.Theme = LightTheme.IsChecked == true ? "Light" : "Dark";
// Guardar fuente
if (FontSizeCombo.SelectedItem != null)
settings.Appearance.FontSize = (int)FontSizeCombo.SelectedItem;
if (FontFamilyCombo.SelectedItem != null)
settings.Appearance.FontFamily = FontFamilyCombo.SelectedItem.ToString();
// Guardar opacidad
settings.Appearance.OpacityDelay = (int)(OpacityDelaySlider.Value * 1000);
// Guardar comportamiento
settings.Behavior.AutoSaveHistory = AutoSaveCheckbox.IsChecked ?? false;
settings.Behavior.ShowNotifications = ShowNotificationsCheckbox.IsChecked ?? true;
settings.Behavior.AutoCopyToClipboard = AutoCopyCheckbox.IsChecked ?? true;
settings.Behavior.NotificationDuration = (int)(NotificationDurationSlider.Value * 1000);
settings.Save();
DialogResult = true;
Close();
}
catch (Exception ex)
{
MessageBox.Show(
$"Error al guardar la configuración: {ex.Message}",
"Error",
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
}
}

84
ThemesAppTheme.xaml Normal file
View File

@ -0,0 +1,84 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Color Palette -->
<Color x:Key="PrimaryColor">#2196F3</Color>
<Color x:Key="PrimaryLightColor">#BBDEFB</Color>
<Color x:Key="PrimaryDarkColor">#1976D2</Color>
<Color x:Key="AccentColor">#FF4081</Color>
<Color x:Key="TextPrimaryColor">#212121</Color>
<Color x:Key="TextSecondaryColor">#757575</Color>
<Color x:Key="DividerColor">#BDBDBD</Color>
<!-- Brushes -->
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
<SolidColorBrush x:Key="PrimaryLightBrush" Color="{StaticResource PrimaryLightColor}"/>
<SolidColorBrush x:Key="PrimaryDarkBrush" Color="{StaticResource PrimaryDarkColor}"/>
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
<SolidColorBrush x:Key="TextPrimaryBrush" Color="{StaticResource TextPrimaryColor}"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="{StaticResource TextSecondaryColor}"/>
<SolidColorBrush x:Key="DividerBrush" Color="{StaticResource DividerColor}"/>
<!-- Button Styles -->
<Style x:Key="DefaultButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Padding" Value="15,5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource PrimaryDarkBrush}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource PrimaryDarkBrush}"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect ShadowDepth="0" BlurRadius="10" Color="#40000000"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Close Button Style -->
<Style x:Key="CloseButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="10">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#20000000"/>
<Setter Property="Foreground" Value="{StaticResource AccentBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>

109
UserSettings.cs Normal file
View File

@ -0,0 +1,109 @@
using System;
using System.IO;
using Newtonsoft.Json;
using System.Diagnostics;
namespace GTPCorrgir
{
public class UserSettings
{
private static readonly string SettingsPath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "usersettings.json");
private static UserSettings instance;
private static readonly object lockObject = new object();
public class WindowSettings
{
public double Left { get; set; }
public double Top { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public double Opacity { get; set; }
}
public class AppearanceSettings
{
public string Theme { get; set; } = "Light";
public int FontSize { get; set; } = 14;
public string FontFamily { get; set; } = "Segoe UI";
public int OpacityDelay { get; set; } = 10000; // milisegundos
}
public class BehaviorSettings
{
public bool AutoSaveHistory { get; set; } = true;
public bool ShowNotifications { get; set; } = true;
public int NotificationDuration { get; set; } = 5000; // milisegundos
public bool AutoCopyToClipboard { get; set; } = true;
}
public WindowSettings Window { get; set; }
public AppearanceSettings Appearance { get; set; }
public BehaviorSettings Behavior { get; set; }
// Constructor público con valores por defecto
public UserSettings()
{
Window = new WindowSettings();
Appearance = new AppearanceSettings();
Behavior = new BehaviorSettings();
}
public static UserSettings Instance
{
get
{
if (instance == null)
{
lock (lockObject)
{
instance ??= Load();
}
}
return instance;
}
}
private static UserSettings Load()
{
try
{
if (File.Exists(SettingsPath))
{
string json = File.ReadAllText(SettingsPath);
var settings = JsonConvert.DeserializeObject<UserSettings>(json);
if (settings != null)
{
return settings;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error loading settings: {ex.Message}");
}
return new UserSettings();
}
public void Save()
{
try
{
string json = JsonConvert.SerializeObject(this, Formatting.Indented);
File.WriteAllText(SettingsPath, json);
}
catch (Exception ex)
{
Debug.WriteLine($"Error saving settings: {ex.Message}");
}
}
public void ResetToDefaults()
{
Window = new WindowSettings();
Appearance = new AppearanceSettings();
Behavior = new BehaviorSettings();
}
}
}

808
gtpask.cs
View File

@ -5,12 +5,10 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Threading;
using LanguageDetection; using LanguageDetection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.Diagnostics; using System.Diagnostics;
using System.Windows.Interop;
using libObsidean; using libObsidean;
namespace GTPCorrgir namespace GTPCorrgir
@ -27,463 +25,521 @@ namespace GTPCorrgir
public ApiKeySection ApiKeys { get; set; } public ApiKeySection ApiKeys { get; set; }
} }
public class gtpask public class gtpask : IDisposable
{ {
private readonly string openAiApiKey; private string _openAiApiKey;
private readonly string groqApiKey; private string _groqApiKey;
private readonly string grokApiKey; private string _grokApiKey;
public Logger Log = new Logger(); private readonly HttpClient _httpClient;
private Dictionary<string, string> languageMap = new Dictionary<string, string> private bool _disposed;
private readonly LanguageDetector _languageDetector;
private readonly Obsidean _markdownProcessor;
public Logger Log { get; }
private readonly Dictionary<string, string> _languageMap = new Dictionary<string, string>
{ {
{ "en", "English" }, { "en", "English" },
{ "es", "Spanish" }, { "es", "Spanish" },
{ "it", "Italian" }, { "it", "Italian" },
{ "pt", "Portuguese" } { "pt", "Portuguese" }
// Agrega más idiomas según sea necesario
}; };
public string IdiomaDetectado; public string IdiomaDetectado { get; private set; }
public string TextoACorregir; public string TextoACorregir { get; set; }
public string TextoCorregido; public string TextoCorregido { get; private set; }
public string TextodeSistema; public string TextodeSistema { get; private set; }
private const bool Simulacion = false; private const bool Simulacion = false;
public gtpask() public gtpask()
{ {
try try
{ {
string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json"); Log = new Logger();
if (File.Exists(configPath)) _httpClient = new HttpClient();
{ _languageDetector = new LanguageDetector();
string jsonContent = File.ReadAllText(configPath); _languageDetector.AddLanguages("en", "es", "it", "pt");
var settings = JsonConvert.DeserializeObject<ApiSettings>(jsonContent); _markdownProcessor = new Obsidean();
openAiApiKey = settings.ApiKeys.OpenAI; LoadApiKeys();
groqApiKey = settings.ApiKeys.Groq; InitializeHttpClient();
grokApiKey = settings.ApiKeys.Grok; _markdownProcessor.LeerPalabrasTecnicas();
if (string.IsNullOrEmpty(openAiApiKey) || string.IsNullOrEmpty(groqApiKey) || string.IsNullOrEmpty(grokApiKey)) Log.Log("gtpask initialized successfully");
{
Log.Log("Warning: One or more API keys are missing in the configuration file.");
}
}
else
{
Log.Log("Error: Configuration file (appsettings.json) not found.");
throw new FileNotFoundException("Configuration file (appsettings.json) not found.");
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Log($"Error loading configuration: {ex.Message}"); Log.Log($"Error initializing gtpask: {ex.Message}");
throw new ApplicationException("Failed to initialize gtpask", ex);
}
}
private void LoadApiKeys()
{
try
{
string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "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)
{
Log.Log($"Error loading API keys: {ex.Message}");
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");
}
public async Task CorregirTexto()
{
if (string.IsNullOrWhiteSpace(TextoACorregir))
{
Log.Log("No hay texto para corregir");
return;
}
try
{
Log.Log("Iniciando proceso de corrección");
Log.Log($"Texto original: {TextoACorregir}");
if (!DetectarIdioma())
{
throw new ApplicationException("No se pudo detectar el idioma del texto");
}
string textoMarcado = MarcarPalabrasTecnicas();
Log.Log($"Texto marcado: {textoMarcado}");
if (Simulacion)
{
await SimularCorreccion();
}
else
{
await ProcesarTextoConLLM(textoMarcado);
}
Log.Log($"Texto corregido: {TextoCorregido}");
}
catch (Exception ex)
{
Log.Log($"Error en CorregirTexto: {ex.Message}");
throw; throw;
} }
} }
private bool DetectarIdioma()
{
try
{
if (EsModoTraduccion())
{
IdiomaDetectado = ObtenerIdiomaObjetivo();
Log.Log($"Modo traducción: idioma objetivo establecido a {IdiomaDetectado}");
return true;
}
string detectedLanguageCode = _languageDetector.Detect(TextoACorregir);
IdiomaDetectado = _languageMap.GetValueOrDefault(detectedLanguageCode, "Desconocido");
Log.Log($"Idioma detectado: {IdiomaDetectado}");
return IdiomaDetectado != "Desconocido";
}
catch (Exception ex)
{
Log.Log($"Error al detectar idioma: {ex.Message}");
return false;
}
}
private bool EsModoTraduccion()
{
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;
}
private string ObtenerIdiomaObjetivo()
{
return Opciones.Instance.modo switch
{
Opciones.modoDeUso.Traducir_a_Ingles => _languageMap["en"],
Opciones.modoDeUso.Traducir_a_Italiano => _languageMap["it"],
Opciones.modoDeUso.Traducir_a_Espanol => _languageMap["es"],
_ => throw new ArgumentException("Modo de traducción no válido")
};
}
private string MarcarPalabrasTecnicas()
{
try
{
return _markdownProcessor.MarkTechnicalTerms_IgnoreCase(TextoACorregir);
}
catch (Exception ex)
{
Log.Log($"Error al marcar palabras técnicas: {ex.Message}");
throw;
}
}
private async Task ProcesarTextoConLLM(string textoMarcado)
{
try
{
string respuestaLLM;
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;
default:
throw new ArgumentException("LLM no válido");
}
if (string.IsNullOrEmpty(respuestaLLM))
{
throw new ApplicationException("No se recibió respuesta del LLM");
}
ProcesarRespuestaLLM(respuestaLLM);
}
catch (Exception ex)
{
Log.Log($"Error al procesar texto con LLM: {ex.Message}");
throw;
}
}
private void ProcesarRespuestaLLM(string respuestaLLM)
{
TextoCorregido = ExtraerValorUnicoJSON(respuestaLLM);
if (TextoCorregido == null)
{
throw new ApplicationException("Error al extraer el texto corregido de la respuesta JSON");
}
TextoCorregido = _markdownProcessor.RemoveTechnicalTermMarkers_IgnoreCase(TextoCorregido).Trim('"');
}
private async Task SimularCorreccion()
{
await Task.Delay(1000);
TextoCorregido = "Texto simulado de prueba";
Log.Log("Simulación completada");
}
public string ExtraerValorUnicoJSON(string input)
{
try
{
int startJson = input.IndexOf('{');
int endJson = input.LastIndexOf('}') + 1;
if (startJson == -1 || endJson == -1 || endJson <= startJson)
{
Log.Log("Formato JSON inválido en la respuesta");
return null;
}
string jsonString = input[startJson..endJson];
JObject jsonObject = JObject.Parse(jsonString);
var firstField = jsonObject.Properties().FirstOrDefault();
return firstField?.Value?.ToString();
}
catch (JsonException ex)
{
Log.Log($"Error al procesar JSON: {ex.Message}");
return null;
}
}
private string CrearMensajeDeSistema() private string CrearMensajeDeSistema()
{ {
switch (Opciones.Instance.modo) return Opciones.Instance.modo switch
{ {
case Opciones.modoDeUso.Corregir: Opciones.modoDeUso.Corregir =>
return "You are an engineer working in industrial automation. Your task is to review texts and rewrite them in a simple and concise manner, making sure to preserve important technical terms and markdown language if present. Please rewrite the following text in " + IdiomaDetectado + " and respond in the following JSON format: { \"Rewritten_text\": \"Your text here\" }."; "You are an engineer working in industrial automation. Your task is to review texts and rewrite them in a simple and concise manner, making sure to preserve important technical terms and markdown language if present. Please rewrite the following text in " + IdiomaDetectado + " and respond in the following JSON format: { \"Rewritten_text\": \"Your text here\" }.",
case Opciones.modoDeUso.Ortografia:
return "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, making sure to preserve important technical terms and markdown language if present. Please write in " + IdiomaDetectado + " and respond in the following JSON format: { \"Rewritten_text\": \"Your text here\" }."; Opciones.modoDeUso.Ortografia =>
default: "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, making sure to preserve important technical terms and markdown language if present. Please write in " + IdiomaDetectado + " and respond in the following JSON format: { \"Rewritten_text\": \"Your text here\" }.",
return "You are an engineer working specialiazed industrial automation. Please answer the following question in " + IdiomaDetectado + " and respond in the following JSON format: { \"Reply_text\": \"Your text here\" }.";
} _ => "You are an engineer working specialiazed industrial automation. Please answer the following question in " + IdiomaDetectado + " and respond in the following JSON format: { \"Reply_text\": \"Your text here\" }."
};
} }
private string CrearMensajeDeUsuario(string texto) private string CrearMensajeDeUsuario(string texto) =>
Opciones.Instance.modo switch
{ {
switch (Opciones.Instance.modo) Opciones.modoDeUso.Corregir =>
{ $"Please rewrite and improve the following text to make it clearer and more concise the words inside brackets are technical words: \"{texto}\"",
case Opciones.modoDeUso.Corregir:
return "Please rewrite and improve the following text to make it clearer and more concise the words inside brackets are technical words: \"" + texto + "\"";
case Opciones.modoDeUso.Ortografia:
return "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 + "\"";
case Opciones.modoDeUso.Traducir_a_Ingles:
return "Please check the following text for spelling errors and provide the corrected version in English. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"" + texto + "\"";
case Opciones.modoDeUso.Traducir_a_Italiano:
return "Please check the following text for spelling errors and provide the corrected version in Italian. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"" + texto + "\"";
case Opciones.modoDeUso.Traducir_a_Espanol:
return "Please check the following text for spelling errors and provide the corrected version in Spanish. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"" + texto + "\"";
default: Opciones.modoDeUso.Ortografia =>
return texto; $"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.Traducir_a_Ingles =>
$"Please check the following text for spelling errors and provide the corrected version in English. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"{texto}\"",
private string DetectarIdioma(string TextoACorregir) Opciones.modoDeUso.Traducir_a_Italiano =>
{ $"Please check the following text for spelling errors and provide the corrected version in Italian. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"{texto}\"",
if (Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Ingles)
return languageMap["en"];
if (Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Italiano)
return languageMap["it"];
if (Opciones.Instance.modo == Opciones.modoDeUso.Traducir_a_Espanol)
return languageMap["es"];
LanguageDetector detector = new LanguageDetector(); Opciones.modoDeUso.Traducir_a_Espanol =>
$"Please check the following text for spelling errors and provide the corrected version in Spanish. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"{texto}\"",
detector.AddLanguages("en", "es", "it");
string detectedLanguageCode = detector.Detect(TextoACorregir);
string detectedLanguageName = languageMap.ContainsKey(detectedLanguageCode)
? languageMap[detectedLanguageCode]
: "Desconocido";
return detectedLanguageName;
}
private bool DetectarIdioma()
{
if (TextoACorregir.Length>0)
{
IdiomaDetectado = DetectarIdioma(TextoACorregir);
Log.Log("Idioma: " + IdiomaDetectado);
if(IdiomaDetectado != "Desconocido")
return true;
else return false;
}
return false;
}
public async Task CorregirTexto()
{
if (Simulacion)
TextoACorregir = "\t\t* Sic : Synchronism?\r\n\t\t* Cont ? \r\n\t\t* Sensor for tensioning?\r\n\t\t* Max ? Overspeed?\r\n\t\t* Min : Stop / Start\r\n\t\t* Variable Speed. - = reference by filler\r\n\t\t* Formats: ? 66mm a 93mm => 27mm. How is changed?";
Log.Log("");
Log.Log("Texto a corregir: " + TextoACorregir);
if (DetectarIdioma()) {
if (IdiomaDetectado == "Desconocido" || this.TextoACorregir.Length == 0)
{
// Nada que hacer
TextoCorregido = TextoACorregir;
return;
}
var md = new Obsidean();
md.LeerPalabrasTecnicas();
TextoACorregir = md.MarkTechnicalTerms_IgnoreCase(TextoACorregir);
Log.Log("Texto marcado: " + TextoACorregir);
string RespuestaLLM = "";
if (!Simulacion)
{
if (Opciones.Instance.LLM == Opciones.LLM_a_Usar.OpenAI) RespuestaLLM = await CallOpenAiApi(TextoACorregir);
if (Opciones.Instance.LLM == Opciones.LLM_a_Usar.Ollama) RespuestaLLM = await CallOllamaApi(TextoACorregir);
if (Opciones.Instance.LLM == Opciones.LLM_a_Usar.Groq) RespuestaLLM = await CallGroqAiApi(TextoACorregir);
if (Opciones.Instance.LLM == Opciones.LLM_a_Usar.Grok) RespuestaLLM = await CallGrokApi(TextoACorregir);
} else
{
await Task.Delay(1000);
RespuestaLLM = "Here is the rewritten text in a clearer and more concise manner:\r\n\r\n{\r\n'Rewritten_text': '\r\n### FB Tilting System Overview\r\n\r\nThe FB Tilting system consists of two main components:\r\n\r\n* **FB Tilting**: The main machine responsible for tilting and moving bottles.\r\n* **Sic (Synchronism)**: Ensures synchronized movement of the tilting machine with other system components.\r\n\r\n### Key Components and Functions\r\n\r\n* **Cont (Controller)**: The controlling unit that regulates the system's operation.\r\n* **Sensor for Tensioning**: Monitors and adjusts the tension of the moving parts.\r\n* **Max (Maximum Speed) and Overspeed Protection**: Safeguards the system from excessive speeds.\r\n* **Min (Minimum Speed) and Stop/Start Function**: Manages the system's startup and shutdown sequences.\r\n* **Variable Speed**: Allows for adjustable speed control, referenced by the filling machine.\r\n\r\n### Format Adaptation\r\n\r\nThe system accommodates various formats, including:\r\n* 66mm to 93mm, which are adapted to 27mm formats. The format change process is implemented as follows:\r\n\r\n### Startup Sequence\r\n\r\nThe startup procedure involves:\r\n\r\n1. **Fill to Sic with Minimum Accumulation**: Fills the Sic component with a minimum amount of material.\r\n2. **Run Chain at Fixed Speed**: Operates the chain at a constant speed.\r\n3. **Wait for Phase to Start**: Waits for the phase and ramp of the doser to be parameterized.\r\n4. **Ramp to Variable Speed**: Gradually adjusts the speed to the selected variable speed setting after a few bottles have been processed.'\r\n}";
}
Log.Log("Respuesta: " + RespuestaLLM);
TextoCorregido = ExtraerValorUnicoJSON(RespuestaLLM);
if (TextoCorregido is null)
TextoCorregido = "Error en la respuesta.";
// Elimina comillas al principio y al final si existen
TextoCorregido = md.RemoveTechnicalTermMarkers_IgnoreCase(TextoCorregido).Trim('\"');
Log.Log("Texto corregido: " + TextoCorregido);
}
}
public string ExtraerValorUnicoJSON(string input)
{
// Encuentra los índices del inicio y del final del JSON
int startJson = input.IndexOf('{');
int endJson = input.LastIndexOf('}') + 1;
if (startJson == -1 || endJson == -1 || endJson <= startJson)
{
return "No valid JSON found in the input string.";
}
// Extrae solo la parte JSON de la entrada
string jsonString = input.Substring(startJson, endJson - startJson);
try
{
// Parsea el JSON
JObject jsonObject = JObject.Parse(jsonString);
// Obtiene el primer campo independientemente del nombre de la clave
var firstField = jsonObject.Properties().FirstOrDefault();
if (firstField != null)
{
return firstField.Value.ToString(); // Devuelve el valor del primer campo
}
else
{
return "JSON does not contain any data.";
}
}
catch (JsonException jsonEx)
{
// Maneja errores de parseo de JSON
return "Error parsing JSON: " + jsonEx.Message;
}
}
static string ExtractCorrectedText(string input)
{
try
{
// Encuentra el índice del inicio y del final del JSON
int startJson = input.IndexOf('{');
int endJson = input.LastIndexOf('}') + 1;
if (startJson == -1 || endJson == -1 || endJson <= startJson)
{
throw new Exception("No valid JSON found in the input string.");
}
// Extrae solo la parte JSON de la entrada
string jsonString = input.Substring(startJson, endJson - startJson);
// Busca el inicio del texto después de "Rewritten_text':"
int startKey = jsonString.IndexOf("'Rewritten_text':") + 17; // 17 es la longitud de "'Rewritten_text':"
if (startKey == -1)
{
throw new Exception("Key 'Rewritten_text' not found.");
}
// Ajusta para encontrar el inicio del texto después de las comillas simples adicionales
int startText = jsonString.IndexOf('\'', startKey) + 1;
int endText = jsonString.LastIndexOf('\'');
if (startText == -1 || endText == -1 || endText <= startText)
{
throw new Exception("No valid text found in the JSON string.");
}
// Extrae el texto entre las comillas simples
string rewrittenText = jsonString.Substring(startText, endText - startText);
return rewrittenText;
}
catch (Exception ex)
{
Console.WriteLine("An error occurred: " + ex.Message);
return null;
}
}
_ => texto
};
private async Task<string> CallGrokApi(string input) private async Task<string> CallGrokApi(string input)
{ {
using var httpClient = new HttpClient(); try
string Mensaje_Sistema = CrearMensajeDeSistema(); {
string Mensaje_Usuario = CrearMensajeDeUsuario(input); _httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_grokApiKey}");
// Configurar headers
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {grokApiKey}");
// Crear el objeto de solicitud siguiendo exactamente el formato del ejemplo curl
var requestData = new var requestData = new
{ {
messages = new[] messages = new[]
{ {
new { role = "system", content = Mensaje_Sistema }, new { role = "system", content = CrearMensajeDeSistema() },
new { role = "user", content = Mensaje_Usuario } new { role = "user", content = CrearMensajeDeUsuario(input) }
}, },
model = "grok-beta", model = "grok-beta",
stream = false, stream = false,
temperature = 0 temperature = 0
}; };
return await EnviarSolicitudLLM("https://api.x.ai/v1/chat/completions", requestData);
}
catch (Exception ex)
{
Log.Log($"Error en llamada a Grok API: {ex.Message}");
throw;
}
}
private async Task<string> CallOllamaApi(string input)
{
try
{
var requestData = new
{
model = "llama3.2:latest",
messages = new[]
{
new { role = "system", content = CrearMensajeDeSistema() },
new { role = "user", content = CrearMensajeDeUsuario(input) }
},
stream = false
};
return await EnviarSolicitudLLM("http://127.0.0.1:11434/api/chat", requestData);
}
catch (Exception ex)
{
Log.Log($"Error en llamada a Ollama API: {ex.Message}");
throw;
}
}
private async Task<string> CallOpenAiApi(string input)
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_openAiApiKey}");
var requestData = new
{
model = "gpt-4o-mini",
messages = new[]
{
new { role = "system", content = CrearMensajeDeSistema() },
new { role = "user", content = CrearMensajeDeUsuario(input) }
}
};
return await EnviarSolicitudLLM("https://api.openai.com/v1/chat/completions", requestData);
}
catch (Exception ex)
{
Log.Log($"Error en llamada a OpenAI API: {ex.Message}");
throw;
}
}
private async Task<string> CallGroqAiApi(string input)
{
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 = CrearMensajeDeSistema() },
new { role = "user", content = CrearMensajeDeUsuario(input) }
},
max_tokens = 2048,
stream = false
};
return await EnviarSolicitudLLM("https://api.groq.com/openai/v1/chat/completions", requestData);
}
catch (Exception ex)
{
Log.Log($"Error en llamada a Groq API: {ex.Message}");
throw;
}
}
private async Task<string> EnviarSolicitudLLM(string endpoint, object requestData)
{
try
{
var content = new StringContent( var content = new StringContent(
JsonConvert.SerializeObject(requestData), JsonConvert.SerializeObject(requestData),
Encoding.UTF8, Encoding.UTF8,
"application/json" "application/json"
); );
try Log.Log($"Enviando solicitud a {endpoint}");
{ Log.Log($"Datos de solicitud: {JsonConvert.SerializeObject(requestData)}");
Log.Log("Ask Grok: " + JsonConvert.SerializeObject(requestData));
// URL corregida para coincidir con el ejemplo using var response = await _httpClient.PostAsync(endpoint, content);
var response = await httpClient.PostAsync("https://api.x.ai/v1/chat/completions", content);
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync(); var responseContent = await response.Content.ReadAsStringAsync();
Log.Log("Grok Response: " + jsonResponse); // Logging la respuesta para debug Log.Log($"Respuesta recibida: {responseContent}");
dynamic data = JsonConvert.DeserializeObject(jsonResponse); if (!response.IsSuccessStatusCode)
return data.choices[0].message.content;
}
catch (HttpRequestException e)
{ {
Log.Log($"Error en la solicitud HTTP: {e.Message}"); throw new HttpRequestException(
Console.WriteLine($"Error making HTTP request: {e.Message}"); $"Error en la solicitud HTTP: {response.StatusCode} - {responseContent}"
return null; );
}
catch (JsonException e)
{
Log.Log($"Error al procesar JSON: {e.Message}");
Console.WriteLine($"Error processing JSON response: {e.Message}");
return null;
}
catch (Exception e)
{
Log.Log($"Error general: {e.Message}");
Console.WriteLine($"An error occurred: {e.Message}");
return null;
}
finally
{
httpClient?.Dispose();
}
} }
var data = JsonConvert.DeserializeObject<dynamic>(responseContent);
private async Task<string> CallOllamaApi(string input) // Manejar diferentes formatos de respuesta según el LLM
if (endpoint.Contains("ollama"))
{ {
var httpClient = new HttpClient();
string Mensaje_Sistema = CrearMensajeDeSistema();
string Mensaje_Usuario = CrearMensajeDeUsuario(input);
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {openAiApiKey}");
var requestData = new
{
model = "llama3.2:latest", // "phi3", //"llama3",
messages = new[]
{
new { role = "system", content = Mensaje_Sistema },
new { role = "user", content = Mensaje_Usuario }
},
stream = false
};
var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
try
{
Log.Log("Ask Ollama: " + JsonConvert.SerializeObject(requestData));
var response = await httpClient.PostAsync("http://127.0.0.1:11434/api/chat", content);
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(jsonResponse);
if (data.done == true && data.message != null) if (data.done == true && data.message != null)
{ {
return data.message.content; return data.message.content;
} }
return "No hubo respuesta del asistente o la sesión aún no ha concluido."; throw new ApplicationException("Formato de respuesta de Ollama inválido");
} }
catch (HttpRequestException e) else // OpenAI, Groq, Grok
{ {
// Captura errores en la solicitud HTTP, como problemas de red o respuestas de error HTTP. if (data.choices != null && data.choices.Count > 0)
Console.WriteLine($"Error making HTTP request: {e.Message}");
return null;
}
catch (Exception e)
{ {
// Captura cualquier otro error
Console.WriteLine($"An error occurred: {e.Message}");
return null;
}
}
private async Task<string> CallOpenAiApi(string input)
{
var httpClient = new HttpClient();
string Mensaje_Sistema = CrearMensajeDeSistema();
string Mensaje_Usuario = CrearMensajeDeUsuario(input);
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {openAiApiKey}");
var requestData = new
{
model = "gpt-4o-mini", //"gpt -4",
messages = new[]
{
new { role = "system", content = Mensaje_Sistema },
new { role = "user", content = Mensaje_Usuario }
}
};
var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
try
{
Log.Log("Ask OpenAI: " + JsonConvert.SerializeObject(requestData));
var response = await httpClient.PostAsync("https://api.openai.com/v1/chat/completions", content);
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(jsonResponse);
return data.choices[0].message.content; return data.choices[0].message.content;
} }
catch (HttpRequestException e) throw new ApplicationException("No se encontró contenido en la respuesta del LLM");
{
// Captura errores en la solicitud HTTP, como problemas de red o respuestas de error HTTP.
Console.WriteLine($"Error making HTTP request: {e.Message}");
return null;
} }
catch (Exception e) }
catch (Exception ex)
{ {
// Captura cualquier otro error Log.Log($"Error al enviar solicitud a {endpoint}: {ex.Message}");
Console.WriteLine($"An error occurred: {e.Message}"); throw;
return null; }
} }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
} }
private async Task<string> CallGroqAiApi(string input) protected virtual void Dispose(bool disposing)
{ {
var httpClient = new HttpClient(); if (!_disposed)
string Mensaje_Sistema = CrearMensajeDeSistema();
string Mensaje_Usuario = CrearMensajeDeUsuario(input);
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {groqApiKey}");
var requestData = new
{ {
model = "llama-3.1-70b-versatile", // "llama3-70b-8192", if (disposing)
messages = new[]
{ {
new { role = "system", content = Mensaje_Sistema }, _httpClient?.Dispose();
new { role = "user", content = Mensaje_Usuario }
},
max_tokens = 2048,
stream = false
};
var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "application/json");
try
{
Log.Log("Ask Groq: " + JsonConvert.SerializeObject(requestData));
var response = await httpClient.PostAsync("https://api.groq.com/openai/v1/chat/completions", content);
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(jsonResponse);
return data.choices[0].message.content;
}
catch (HttpRequestException e)
{
// Captura errores en la solicitud HTTP, como problemas de red o respuestas de error HTTP.
Console.WriteLine($"Error making HTTP request: {e.Message}");
return null;
}
catch (Exception e)
{
// Captura cualquier otro error
Console.WriteLine($"An error occurred: {e.Message}");
return null;
} }
_disposed = true;
}
} }
~gtpask()
{
Dispose(false);
}
}
// Clase auxiliar para manejar excepciones específicas de la aplicación
public class LLMException : Exception
{
public LLMException(string message) : base(message) { }
public LLMException(string message, Exception innerException) : base(message, innerException) { }
}
// Clase auxiliar para validación
public static class Validations
{
public static void ValidateNotNull(object value, string paramName)
{
if (value == null)
{
throw new ArgumentNullException(paramName);
}
}
public static void ValidateNotNullOrEmpty(string value, string paramName)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Value cannot be null or empty", paramName);
}
}
} }
} }

View File

@ -1,13 +1,63 @@
<Window x:Class="GTPCorrgir.notificacion" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" <Window x:Class="GTPCorrgir.notificacion" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="100" Width="300" Topmost="True" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="100" Width="300" Topmost="True"
ShowInTaskbar="False" WindowStyle="None" AllowsTransparency="True" Background="Transparent"> ShowInTaskbar="False" WindowStyle="None" AllowsTransparency="True" Background="Transparent">
<Window.Resources>
<!-- Animaciones -->
<Storyboard x:Key="FadeIn">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:0.3" />
</Storyboard>
<Storyboard x:Key="FadeOut">
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:0.3" />
</Storyboard>
<!-- Estilo del botón de cerrar -->
<Style x:Key="CloseButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="#757575" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Width" Value="20" />
<Setter Property="Height" Value="20" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="10">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#20000000" />
<Setter Property="Foreground" Value="#FF4081" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#40000000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Border CornerRadius="10" Background="#AAF0F0F0"> <Border CornerRadius="10" Background="#AAF0F0F0">
<StackPanel> <Grid>
<TextBlock x:Name="TitleText" FontSize="16" FontWeight="Bold" Margin="10" /> <Grid.RowDefinitions>
<TextBlock x:Name="MessageText" FontSize="14" Margin="10" /> <RowDefinition Height="Auto" />
<Button Content="Cerrar" Width="75" Height="20" Margin="0" Click="CloseButton_Click" <RowDefinition Height="*" />
HorizontalAlignment="Right" /> <RowDefinition Height="Auto" />
</StackPanel> </Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock x:Name="TitleText" FontSize="16" FontWeight="Bold" Margin="10,10,30,5" />
<Button Content="×" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,5,5,0"
Style="{StaticResource CloseButtonStyle}" Click="CloseButton_Click" />
</Grid>
<TextBlock x:Name="MessageText" Grid.Row="1" FontSize="14" Margin="10,0,10,5" TextWrapping="Wrap" />
<ProgressBar x:Name="AutoCloseProgress" Grid.Row="2" Height="2" Margin="10,0,10,5" Foreground="#FF4081" />
</Grid>
</Border> </Border>
</Window> </Window>

View File

@ -1,41 +1,114 @@
using System.Windows; using System;
using Application = System.Windows.Application; using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace GTPCorrgir namespace GTPCorrgir
{ {
/// <summary>
/// Interaction logic for notificacion.xaml
/// </summary>
public partial class notificacion : Window public partial class notificacion : Window
{ {
private readonly DispatcherTimer autoCloseTimer;
private readonly DispatcherTimer progressTimer;
private double progressValue = 0;
private const int AUTO_CLOSE_SECONDS = 5;
private const int PROGRESS_UPDATE_INTERVAL = 50; // milisegundos
public notificacion() public notificacion()
{ {
InitializeComponent(); InitializeComponent();
PositionWindow(); PositionWindow();
// Configurar el timer para auto-cierre
autoCloseTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(AUTO_CLOSE_SECONDS)
};
autoCloseTimer.Tick += AutoCloseTimer_Tick;
// Configurar el timer para la barra de progreso
progressTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(PROGRESS_UPDATE_INTERVAL)
};
progressTimer.Tick += ProgressTimer_Tick;
// Iniciar animación de entrada
Loaded += (s, e) =>
{
var storyboard = (Storyboard)FindResource("FadeIn");
storyboard.Begin(this);
autoCloseTimer.Start();
progressTimer.Start();
};
} }
private void PositionWindow() private void PositionWindow()
{ {
// Obtener la posición del cursor
var cursorPosition = System.Windows.Forms.Cursor.Position; var cursorPosition = System.Windows.Forms.Cursor.Position;
// Determinar en qué pantalla está el cursor
var screen = System.Windows.Forms.Screen.FromPoint(cursorPosition); var screen = System.Windows.Forms.Screen.FromPoint(cursorPosition);
// Configurar la ubicación de la ventana para que aparezca en la esquina inferior derecha this.Left = screen.WorkingArea.Right - this.Width - 20;
this.Left = screen.WorkingArea.Right - this.Width; this.Top = screen.WorkingArea.Bottom - this.Height - 20;
this.Top = screen.WorkingArea.Bottom - this.Height;
} }
public void UpdateNotification(string title, string message) public void UpdateNotification(string title, string message)
{ {
TitleText.Text = title; TitleText.Text = title;
MessageText.Text = message; MessageText.Text = message;
// Reiniciar timers y progreso
progressValue = 0;
AutoCloseProgress.Value = 0;
autoCloseTimer.Stop();
progressTimer.Stop();
autoCloseTimer.Start();
progressTimer.Start();
}
private async void AutoCloseTimer_Tick(object sender, EventArgs e)
{
autoCloseTimer.Stop();
progressTimer.Stop();
var storyboard = (Storyboard)FindResource("FadeOut");
storyboard.Completed += (s, _) => Close();
storyboard.Begin(this);
}
private void ProgressTimer_Tick(object sender, EventArgs e)
{
progressValue += (PROGRESS_UPDATE_INTERVAL / (AUTO_CLOSE_SECONDS * 1000.0)) * 100;
AutoCloseProgress.Value = progressValue;
if (progressValue >= 100)
{
progressTimer.Stop();
}
} }
private void CloseButton_Click(object sender, RoutedEventArgs e) private void CloseButton_Click(object sender, RoutedEventArgs e)
{ {
Application.Current.Shutdown(); autoCloseTimer.Stop();
progressTimer.Stop();
var storyboard = (Storyboard)FindResource("FadeOut");
storyboard.Completed += (s, _) => Close();
storyboard.Begin(this);
}
protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e)
{
base.OnMouseEnter(e);
autoCloseTimer.Stop();
progressTimer.Stop();
}
protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
{
base.OnMouseLeave(e);
autoCloseTimer.Start();
progressTimer.Start();
} }
} }
} }