diff --git a/App.xaml.cs b/App.xaml.cs index 69e73e8..43051e0 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -81,6 +81,7 @@ 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: @@ -155,9 +156,9 @@ namespace GTPCorrgir $"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.PreguntaRespuesta || 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) +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) { diff --git a/ContextMenuWindow.xaml.cs b/ContextMenuWindow.xaml.cs index 50b5880..bcb5519 100644 --- a/ContextMenuWindow.xaml.cs +++ b/ContextMenuWindow.xaml.cs @@ -47,6 +47,7 @@ namespace GTPCorrgir 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 }, @@ -167,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; @@ -182,7 +189,7 @@ namespace GTPCorrgir try { string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logfile.log"); - + if (File.Exists(logFilePath)) { // Intentar abrir con el programa predeterminado @@ -194,13 +201,13 @@ namespace GTPCorrgir } else { - MessageBox.Show("El archivo de log no existe aún.", "Información", + 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", + MessageBox.Show($"Error al abrir el archivo de log: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } @@ -210,22 +217,22 @@ namespace GTPCorrgir 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, + "¿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(); - + foreach (System.Diagnostics.TraceListener listener in System.Diagnostics.Trace.Listeners) { if (listener is System.Diagnostics.TextWriterTraceListener textListener) @@ -233,7 +240,7 @@ namespace GTPCorrgir textWriterListeners.Add(textListener); } } - + // Cerrar y remover temporalmente los listeners foreach (var listener in textWriterListeners) { @@ -241,53 +248,53 @@ namespace GTPCorrgir 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", + + 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", + "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", + "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", + 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", + MessageBox.Show($"Error al limpiar el archivo de log: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } @@ -307,47 +314,47 @@ namespace GTPCorrgir }; var stackPanel = new StackPanel { Margin = new Thickness(20) }; - - stackPanel.Children.Add(new System.Windows.Controls.TextBlock - { - Text = "GTPCorregir", - FontSize = 18, + + 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", + + 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 - { + + 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 - { + 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 - { + 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 - { + 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 - { + + stackPanel.Children.Add(new System.Windows.Controls.TextBlock + { Text = $"Versión: {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}", FontSize = 10, HorizontalAlignment = System.Windows.HorizontalAlignment.Center, diff --git a/Program.cs b/Program.cs index 734391e..c72ae33 100644 --- a/Program.cs +++ b/Program.cs @@ -38,6 +38,7 @@ namespace GTPCorrgir Traducir_a_Portugues, OCRaTexto, Menu, + ClaudeWebSearch, } public Dictionary nombreLLM = new Dictionary @@ -130,6 +131,8 @@ namespace GTPCorrgir 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")) diff --git a/gtpask.cs b/gtpask.cs index 9edd019..b84c1d2 100644 --- a/gtpask.cs +++ b/gtpask.cs @@ -176,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; } } @@ -227,26 +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; - case Opciones.LLM_a_Usar.Claude: - respuestaLLM = await CallClaudeApi(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)) @@ -254,7 +292,7 @@ namespace GTPCorrgir throw new ApplicationException("No se recibió respuesta del LLM"); } - ProcesarRespuestaLLM(respuestaLLM); + ProcesarRespuestaLLM(respuestaLLM, respuestaCompleta); } catch (Exception ex) { @@ -263,26 +301,141 @@ namespace GTPCorrgir } } - private void ProcesarRespuestaLLM(string respuestaLLM) + private void ProcesarRespuestaLLM(string respuestaLLM, string respuestaCompleta) { - string respuestaExtraida = ExtraerValorUnicoJSON(respuestaLLM); - if (respuestaExtraida == null) + // Para Claude Web Search, la respuesta ya viene en formato de texto final + if (Opciones.Instance.modo == Opciones.modoDeUso.ClaudeWebSearch) + { + // Claude Web Search ya devuelve el texto final con formato JSON interno, + // intentamos extraer JSON primero, si falla usamos el texto completo + string respuestaExtraida = ExtraerValorUnicoJSON(respuestaLLM); + if (respuestaExtraida == null) + { + // Si no se puede extraer JSON, usamos la respuesta completa + 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); + + // Extraer y formatear enlaces de búsqueda web + string enlacesFormateados = ExtraerEnlacesWebSearch(respuestaCompleta); + + // Para el modo Pregunta-Respuesta y Claude Web Search, combinar pregunta original con la respuesta y enlaces + TextoCorregido = $"{TextoACorregir}\n\n{respuestaExtraida}"; + + if (!string.IsNullOrEmpty(enlacesFormateados)) + { + TextoCorregido += $"\n\n## Fuentes consultadas:\n{enlacesFormateados}"; + } + + 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"); } - respuestaExtraida = System.Text.RegularExpressions.Regex.Replace(respuestaExtraida, @"\*\*(.*?)\*\*", "$1"); - respuestaExtraida = _markdownProcessor.RemoveTechnicalTermMarkers_IgnoreCase(respuestaExtraida).Trim('"'); - respuestaExtraida = _markdownProcessor.RemoveDoubleBrackets(respuestaExtraida); + 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{respuestaExtraida}"; + TextoCorregido = $"{TextoACorregir}\n{respuestaExtraidaNormal}"; } else { - TextoCorregido = respuestaExtraida; + 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 enlaces = new List(); + + // Deserializar la respuesta completa para extraer los resultados de búsqueda + var data = JsonConvert.DeserializeObject(respuestaCompleta); + + if (data?.content != null) + { + Log.Log($"ExtraerEnlacesWebSearch: Encontrados {data.content.Count} elementos de contenido"); + + foreach (var contentItem in data.content) + { + Log.Log($"ExtraerEnlacesWebSearch: Procesando elemento tipo: {contentItem.type}"); + + // Buscar elementos de tipo "web_search_tool_result" + if (contentItem.type == "web_search_tool_result" && contentItem.content != null) + { + Log.Log($"ExtraerEnlacesWebSearch: Encontrado web_search_tool_result con {contentItem.content.Count} resultados"); + + 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() ?? ""; + + Log.Log($"ExtraerEnlacesWebSearch: Resultado - Título: {titulo}, URL: {url}"); + + if (!string.IsNullOrEmpty(url)) + { + string enlaceFormateado; + if (!string.IsNullOrEmpty(pageAge)) + { + enlaceFormateado = $"* [{titulo}]({url}) - {pageAge}"; + } + else + { + enlaceFormateado = $"* [{titulo}]({url})"; + } + + enlaces.Add(enlaceFormateado); + } + } + } + } + } + } + else + { + Log.Log("ExtraerEnlacesWebSearch: No se encontró contenido en la respuesta"); + } + + // Eliminar duplicados y devolver como string + var enlacesUnicos = enlaces.Distinct().ToList(); + + Log.Log($"ExtraerEnlacesWebSearch: Extraídos {enlacesUnicos.Count} enlaces únicos"); + + if (enlacesUnicos.Any()) + { + return string.Join("\n", enlacesUnicos); + } + + return ""; + } + catch (Exception ex) + { + Log.Log($"Error al extraer enlaces de web search: {ex.Message}"); + return ""; } } @@ -336,6 +489,17 @@ namespace GTPCorrgir }; } + 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 { @@ -348,6 +512,9 @@ namespace GTPCorrgir 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}\"", @@ -503,6 +670,103 @@ namespace GTPCorrgir } } + private async Task 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(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 EnviarSolicitudLLM(string endpoint, object requestData) { try @@ -519,7 +783,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) { @@ -575,6 +847,58 @@ 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(); + + // Verificar si el texto es principalmente una URL + return texto.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + texto.StartsWith("https://", StringComparison.OrdinalIgnoreCase) || + texto.StartsWith("www.", StringComparison.OrdinalIgnoreCase) || + (texto.Contains(".") && texto.Split(' ').Length == 1); + } + public void Dispose() { Dispose(true);