GTPCorrgir/gtpask.cs

545 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using LanguageDetection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using libObsidean;
namespace GTPCorrgir
{
public class ApiSettings
{
public class ApiKeySection
{
public string OpenAI { get; set; }
public string Groq { get; set; }
public string Grok { get; set; }
}
public ApiKeySection ApiKeys { get; set; }
}
public class gtpask : IDisposable
{
private string _openAiApiKey;
private string _groqApiKey;
private string _grokApiKey;
private readonly HttpClient _httpClient;
private bool _disposed;
private readonly LanguageDetector _languageDetector;
private readonly Obsidean _markdownProcessor;
public Logger Log { get; }
private readonly Dictionary<string, string> _languageMap = new Dictionary<string, string>
{
{ "en", "English" },
{ "es", "Spanish" },
{ "it", "Italian" },
{ "pt", "Portuguese" }
};
public string IdiomaDetectado { get; private set; }
public string TextoACorregir { get; set; }
public string TextoCorregido { get; private set; }
public string TextodeSistema { get; private set; }
private const bool Simulacion = false;
public gtpask()
{
try
{
Log = new Logger();
_httpClient = new HttpClient();
_languageDetector = new LanguageDetector();
_languageDetector.AddLanguages("en", "es", "it", "pt");
_markdownProcessor = new Obsidean();
LoadApiKeys();
InitializeHttpClient();
_markdownProcessor.LeerPalabrasTecnicas();
Log.Log("gtpask initialized successfully");
}
catch (Exception ex)
{
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;
}
}
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()
{
return Opciones.Instance.modo switch
{
Opciones.modoDeUso.Corregir =>
"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\" }.",
Opciones.modoDeUso.Ortografia =>
"Please check the following text for spelling errors and provide the corrected version. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find, 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\" }.",
_ => "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) =>
Opciones.Instance.modo switch
{
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}\"",
Opciones.modoDeUso.Ortografia =>
$"Please check the following text for spelling errors and provide the corrected version. Do not change the meaning or structure of the sentences. Only correct any spelling mistakes you find on: \"{texto}\"",
Opciones.modoDeUso.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}\"",
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}\"",
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}\"",
_ => texto
};
private async Task<string> CallGrokApi(string input)
{
try
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_grokApiKey}");
var requestData = new
{
messages = new[]
{
new { role = "system", content = CrearMensajeDeSistema() },
new { role = "user", content = CrearMensajeDeUsuario(input) }
},
model = "grok-beta",
stream = false,
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(
JsonConvert.SerializeObject(requestData),
Encoding.UTF8,
"application/json"
);
Log.Log($"Enviando solicitud a {endpoint}");
Log.Log($"Datos de solicitud: {JsonConvert.SerializeObject(requestData)}");
using var response = await _httpClient.PostAsync(endpoint, content);
var responseContent = await response.Content.ReadAsStringAsync();
Log.Log($"Respuesta recibida: {responseContent}");
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Error en la solicitud HTTP: {response.StatusCode} - {responseContent}"
);
}
var data = JsonConvert.DeserializeObject<dynamic>(responseContent);
// Manejar diferentes formatos de respuesta según el LLM
if (endpoint.Contains("ollama"))
{
if (data.done == true && data.message != null)
{
return data.message.content;
}
throw new ApplicationException("Formato de respuesta de Ollama inválido");
}
else // OpenAI, Groq, Grok
{
if (data.choices != null && data.choices.Count > 0)
{
return data.choices[0].message.content;
}
throw new ApplicationException("No se encontró contenido en la respuesta del LLM");
}
}
catch (Exception ex)
{
Log.Log($"Error al enviar solicitud a {endpoint}: {ex.Message}");
throw;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_httpClient?.Dispose();
}
_disposed = true;
}
}
~gtpask()
{
Dispose(false);
}
}
// Clase auxiliar para manejar excepciones específicas de la aplicación
public class LLMException : Exception
{
public LLMException(string message) : base(message) { }
public LLMException(string message, Exception innerException) : base(message, innerException) { }
}
// Clase auxiliar para validación
public static class Validations
{
public static void ValidateNotNull(object value, string paramName)
{
if (value == null)
{
throw new ArgumentNullException(paramName);
}
}
public static void ValidateNotNullOrEmpty(string value, string paramName)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Value cannot be null or empty", paramName);
}
}
}
}