Compare commits

..

7 Commits

Author SHA1 Message Date
Miguel 7afdbca03a Se añadió una nueva sección en la documentación del backend para describir el uso de servicios compartidos, incluyendo ejemplos de implementación para el `ExcelService`, servicios de detección de idioma y servicios de Modelos de Lenguaje Grandes (LLM). Además, se eliminaron los scripts `x2_io_adaptation_script.py` y `x3_code_snippets_generator.py`, que ya no son necesarios, y se actualizaron los logs para reflejar estos cambios. 2025-07-10 16:10:13 +02:00
Miguel 164667bc2f Se modificó el script `x1_lad_converter.py` para cambiar el manejo de los objetivos de red, pasando de un solo objetivo a una lista de objetivos. Se implementaron mejoras en la lógica de análisis de redes, permitiendo la recopilación de múltiples salidas y optimizando la generación de código SCL. Además, se actualizaron los mensajes de depuración y se mejoró la estructura del código para una mayor claridad y mantenimiento. 2025-07-10 12:20:54 +02:00
Miguel ffc686e140 Se añadieron requisitos de codificación de salida en el archivo de configuración del backend, especificando que la salida estándar debe ser en UTF-8 para evitar problemas de caracteres corruptos. Además, se realizaron mejoras en el script `x1_lad_converter.py`, incluyendo la implementación de clases para gestionar funciones y bloques de funciones, así como la recopilación de interfaces de funciones en la primera pasada del convertidor. Se actualizaron los logs para reflejar la ejecución y los resultados del proceso de conversión. 2025-07-10 11:24:11 +02:00
Miguel b4959e772f Se agregó una nueva ruta en el archivo de configuración del workspace de TwinCat y se realizaron mejoras en el script `x1_lad_converter.py`. Se añadió un nuevo directorio para exportaciones y se optimizó el manejo de expresiones en el convertidor, mejorando la legibilidad y la eficiencia del código. Además, se actualizaron los logs para reflejar la ejecución del script de documentación completa de IOs, incluyendo un resumen de las variables analizadas y los archivos generados. 2025-07-09 16:42:45 +02:00
Miguel 4a1b16117e Actualización de directorios de trabajo y logs en el script de análisis XML
- Se modificaron los directorios de trabajo en `script_config.json` y `work_dir.json` para apuntar a la nueva ubicación de los archivos del proyecto 98050.
- Se actualizaron los logs de ejecución en `log_x0_main.txt` y `log_98050_PLC.txt` para reflejar las nuevas fechas, duraciones y resultados de los procesos de exportación.
- Se corrigieron rutas en varios archivos de configuración para asegurar la correcta ejecución de los scripts.
2025-07-09 14:12:43 +02:00
Miguel 2cec16af0e Se implementó una nueva función para buscar archivos de workspace específicos en un directorio, mejorando la integración con editores como VSCode y Cursor. Además, se actualizaron las configuraciones del archivo de workspace para incluir asociaciones de archivos y recomendaciones de extensiones, optimizando la experiencia del usuario al trabajar con proyectos de Python. 2025-06-23 15:29:22 +02:00
Miguel df6e40e68d Se agregó una nueva ruta API para obtener el archivo de solución (.sln) de proyectos C#, mejorando la gestión de proyectos C# en la aplicación. Además, se implementó la lógica para abrir el archivo de solución específico en Visual Studio 2022, si está disponible, al abrir un proyecto. Se mejoró la notificación al usuario con información sobre el archivo de solución abierto. 2025-06-23 11:13:50 +02:00
30 changed files with 24929 additions and 56523 deletions

9
.cursorignore Normal file
View File

@ -0,0 +1,9 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.development
.env.test
.env.production

View File

@ -2,220 +2,132 @@
## Introducción
Esta guía explica cómo configurar y usar correctamente la función `load_configuration()` en scripts ubicados bajo el directorio `/backend`. La función carga configuraciones desde un archivo `script_config.json` ubicado en el mismo directorio que el script que la llama.
Esta guía explica cómo usar la función `load_configuration()` para cargar parámetros desde un archivo `script_config.json` en los scripts del backend.
## Configuración del Path e Importación
## 1. Configuración del Script
### 1. Configuración estándar del Path
Para que tus scripts puedan encontrar los módulos del proyecto, necesitas añadir el directorio raíz al path de Python.
Para scripts ubicados en subdirectorios bajo `/backend`, usa este patrón estándar:
### Path Setup e Importación
Coloca este código al inicio de tu script:
```python
import os
import sys
# Configurar el path al directorio raíz del proyecto
# Añadir el directorio raíz al sys.path
# El número de `os.path.dirname()` depende de la profundidad del script.
# Para /backend/script_groups/grupo/script.py, son 4.
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
# Importar la función de configuración
# Importar la función
from backend.script_utils import load_configuration
```
**Nota:** El número de `os.path.dirname()` anidados depende de la profundidad del script:
- Scripts en `/backend/script_groups/grupo/`: 4 niveles
- Scripts en `/backend/`: 2 niveles
## 2. Cargar la Configuración
### 2. Importación Correcta
La función `load_configuration()` busca y carga un archivo llamado `script_config.json` que debe estar en el **mismo directorio** que el script que la ejecuta. Devuelve un diccionario con todo el contenido del JSON.
**✅ Correcto:**
```python
from backend.script_utils import load_configuration
```
**❌ Incorrecto:**
```python
from script_utils import load_configuration # No funciona desde subdirectorios
```
## Uso de la Función load_configuration()
### Implementación Básica
### Ejemplo de Uso
```python
def main():
# Cargar configuraciones
# Cargar configuraciones del archivo script_config.json
configs = load_configuration()
# Obtener el directorio de trabajo
working_directory = configs.get("working_directory", "")
# Es buena práctica verificar si la configuración se cargó
if not configs:
print("Error: No se pudo cargar la configuración. Saliendo.")
return
# Acceder a configuraciones por nivel
# Acceder a los parámetros usando .get() para evitar errores
working_directory = configs.get("working_directory", "")
level1_config = configs.get("level1", {})
level2_config = configs.get("level2", {})
level3_config = configs.get("level3", {})
# Ejemplo de uso de parámetros específicos con valores por defecto
# Ejemplo de uso de un parámetro específico con valor por defecto
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
print(f"Directorio de trabajo: {working_directory}")
print(f"Directorio de salida SCL: {scl_output_dir}")
if __name__ == "__main__":
main()
```
### Estructura del Archivo script_config.json
## 3. Archivo `script_config.json`
El archivo `script_config.json` debe estar ubicado en el mismo directorio que el script que llama a `load_configuration()`. Estructura recomendada:
Este archivo contiene los parámetros de tu script. La estructura interna del JSON, como el uso de `"level1"`, `"level2"`, etc., es una convención para organizar los parámetros. `load_configuration()` simplemente lee el archivo y devuelve su contenido.
**Ejemplo de `script_config.json`:**
```json
{
"working_directory": "/ruta/al/directorio/de/trabajo",
"level1": {
"parametro_global_1": "valor1",
"parametro_global_2": "valor2"
"parametro_global_1": "valor1"
},
"level2": {
"scl_output_dir": "scl_output",
"xref_output_dir": "xref_output",
"xref_source_subdir": "source",
"aggregated_filename": "full_project_representation.md"
"xref_output_dir": "xref_output"
},
"level3": {
"parametro_especifico_1": true,
"parametro_especifico_2": 100
"parametro_especifico_1": true
}
}
```
## Ejemplo Completo de Implementación
## 4. Manejo de Errores
```python
"""
Script de ejemplo que demuestra el uso completo de load_configuration()
"""
`load_configuration()` está diseñada para ser robusta:
- Si `script_config.json` no se encuentra, retorna un diccionario vacío `{}`.
- Si el JSON es inválido, imprime un error y también retorna `{}`.
import os
import sys
import json
**Siempre** comprueba si el diccionario devuelto está vacío para manejar estos casos de forma segura en tu script.
# Configuración del path
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
from backend.script_utils import load_configuration
## 5. Jerarquía de Archivos de Configuración
El sistema utiliza un modelo de configuración en cascada para gestionar los parámetros de los scripts. Esta jerarquía permite establecer configuraciones a nivel global, de grupo y de trabajo. Antes de la ejecución, el launcher lee estos archivos, los combina y genera un único `script_config.json` en la carpeta del script. La función `load_configuration()` es la que finalmente lee este archivo consolidado.
def main():
print("=== Cargando Configuración ===")
A continuación se describe la finalidad y ubicación de cada archivo clave.
# Cargar configuraciones
configs = load_configuration()
### Archivos de Valores (Parámetros)
# Verificar que se cargó correctamente
if not configs:
print("Error: No se pudo cargar la configuración")
return
Contienen los datos y variables que utilizará el script. La configuración se superpone en el siguiente orden: `Nivel 1 < Nivel 2 < Nivel 3`.
# Obtener configuraciones
working_directory = configs.get("working_directory", "")
level1_config = configs.get("level1", {})
level2_config = configs.get("level2", {})
level3_config = configs.get("level3", {})
- **`data.json` (Nivel 1 - Global)**
- **Ubicación:** `data/data.json`
- **Utilidad:** Almacena variables globales disponibles para todos los scripts. Ideal para parámetros generales.
- **Acceso:** Sus datos se cargan en la clave `"level1"` del diccionario `configs`.
# Mostrar configuraciones cargadas
print(f"Directorio de trabajo: {working_directory}")
print("Configuración Nivel 1:", json.dumps(level1_config, indent=2))
print("Configuración Nivel 2:", json.dumps(level2_config, indent=2))
print("Configuración Nivel 3:", json.dumps(level3_config, indent=2))
- **`script_config.json` (Nivel 2 - Grupo)**
- **Ubicación:** En la raíz de cada directorio de grupo (ej: `backend/script_groups/MiGrupo/script_config.json`).
- **Utilidad:** Define parámetros compartidos por todos los scripts de un grupo.
- **Acceso:** Sus datos se cargan en la clave `"level2"`.
# Ejemplo de uso de parámetros con valores por defecto
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
- **`work_dir.json` (Nivel 3 - Directorio de Trabajo)**
- **Ubicación:** Dentro del directorio de trabajo que el script va a procesar.
- **Utilidad:** Contiene parámetros para una ejecución específica. Es el nivel más específico y sobrescribe los anteriores.
- **Acceso:** Sus datos se cargan en la clave `"level3"`.
print(f"Directorio de salida SCL: {scl_output_dir}")
print(f"Directorio de salida XREF: {xref_output_dir}")
### Archivos de Esquema (Definiciones de Estructura)
No contienen valores, sino que describen la estructura y tipos de datos que los archivos de valores deben tener. Son usados por el launcher para validación y para generar interfaces de configuración.
if __name__ == "__main__":
main()
```
- **`esquema_group.json`**
- **Ubicación:** Raíz del directorio del grupo.
- **Utilidad:** Define la estructura del `script_config.json` del grupo.
## Manejo de Errores
- **`esquema_work.json`**
- **Ubicación:** Raíz del directorio del grupo.
- **Utilidad:** Define la estructura del `work_dir.json`.
La función `load_configuration()` maneja automáticamente los siguientes casos:
1. **Archivo no encontrado**: Retorna un diccionario vacío `{}`
2. **JSON inválido**: Retorna un diccionario vacío y muestra un mensaje de error
3. **Errores de lectura**: Retorna un diccionario vacío y muestra un mensaje de error
### Verificación de Configuración Válida
```python
configs = load_configuration()
# Verificar que se cargó correctamente
if not configs:
print("Advertencia: No se pudo cargar la configuración, usando valores por defecto")
working_directory = "."
else:
working_directory = configs.get("working_directory", ".")
# Verificar directorio de trabajo
if not os.path.exists(working_directory):
print(f"Error: El directorio de trabajo no existe: {working_directory}")
return
```
## Mejores Prácticas
1. **Siempre proporciona valores por defecto** al usar `.get()`:
```python
valor = config.get("clave", "valor_por_defecto")
```
2. **Verifica la existencia de directorios críticos**:
```python
if not os.path.exists(working_directory):
print(f"Error: Directorio no encontrado: {working_directory}")
return
```
3. **Documenta los parámetros esperados** en tu script:
```python
# Parámetros esperados en level2:
# - scl_output_dir: Directorio de salida para archivos SCL
# - xref_output_dir: Directorio de salida para referencias cruzadas
```
4. **Usa nombres de parámetros consistentes** en todos los scripts del mismo grupo.
## Definición Técnica de load_configuration()
```python
def load_configuration() -> Dict[str, Any]:
"""
Load configuration from script_config.json in the current script directory.
Returns:
Dict containing configurations with levels 1, 2, 3 and working_directory
Example usage in scripts:
from backend.script_utils import load_configuration
configs = load_configuration()
level1_config = configs.get("level1", {})
level2_config = configs.get("level2", {})
level3_config = configs.get("level3", {})
working_dir = configs.get("working_directory", "")
"""
```
La función utiliza `inspect.stack()` para determinar automáticamente el directorio del script que la llama, asegurando que siempre busque el archivo `script_config.json` en la ubicación correcta.
## Documentación de Scripts para el Launcher
## 6. Documentación de Scripts para el Launcher
El sistema de launcher utiliza archivos JSON para mostrar información sobre los grupos de scripts y scripts individuales en la interfaz web.
@ -291,3 +203,208 @@ Para un grupo "XML Parser to SCL":
}
}
```
## 7. Requisitos de Codificación de Salida (stdout)
Toda la salida estándar (`stdout`) generada por los scripts (por ejemplo, mediante la función `print()`) es capturada en tiempo real y mostrada en el panel de logs del frontend.
Para garantizar que el texto se muestre correctamente y evitar caracteres corruptos (mojibake), **la salida de los scripts debe tener codificación UTF-8**.
### Configuración Automática
El sistema está diseñado para facilitar esto. Al ejecutar un script, el entorno se configura automáticamente para que la salida de Python sea UTF-8. Específicamente, se establece la variable de entorno `PYTHONIOENCODING=utf-8`.
Gracias a esto, en la mayoría de los casos, **no necesitas hacer nada especial**. Simplemente usa `print()` y la codificación será la correcta.
### Casos Especiales y Solución de Problemas
Pueden surgir problemas si tu script lee datos de fuentes externas (como archivos) que tienen una codificación diferente. Si imprimes contenido directamente sin decodificarlo primero, podrías enviar bytes no válidos a la salida.
La regla es: **decodifica los datos de entrada a un string de Python lo antes posible, y deja que `print()` se encargue de la codificación de salida.**
**Ejemplo de cómo manejar un archivo con codificación `latin-1`:**
```python
# INCORRECTO: Si el archivo no es UTF-8, esto puede enviar bytes inválidos.
# El launcher intentará decodificarlos como UTF-8 y podría fallar o mostrar basura.
try:
with open('mi_archivo_legacy.txt', 'rb') as f:
print(f.read())
except Exception as e:
print(f"Esto probablemente falle o muestre texto corrupto: {e}")
# CORRECTO: Leer el archivo especificando su codificación para decodificarlo a un string.
# Una vez que es un string de Python, `print` lo manejará correctamente.
try:
with open('mi_archivo_legacy.txt', 'r', encoding='latin-1') as f:
contenido = f.read()
# Ahora `contenido` es un string de Python.
# print() lo codificará a UTF-8 automáticamente gracias a la configuración del entorno.
print(contenido)
except FileNotFoundError:
print("El archivo 'mi_archivo_legacy.txt' no fue encontrado para el ejemplo.")
except Exception as e:
print(f"Error al procesar el archivo: {e}")
```
## 8. Uso de Servicios Compartidos
El proyecto ofrece una serie de servicios reutilizables en el directorio `services/` para tareas comunes como la manipulación de archivos Excel, detección de idioma o traducción.
Para utilizar estos servicios en tu script, asegúrate de que el directorio raíz del proyecto esté en el `sys.path`, como se explica en la sección 1 de esta guía.
### 8.1 Servicio de Excel (`ExcelService`)
El `ExcelService` (`services/excel/excel_service.py`) facilita la lectura y escritura de archivos Excel, con manejo de reintentos (por si el archivo está abierto) y opciones de formato.
**Ejemplo de importación y uso:**
```python
# Asegúrate de tener el path raíz configurado
# ... (código de configuración de sys.path)
from services.excel.excel_service import ExcelService
def main():
excel_service = ExcelService()
# Leer un archivo Excel
try:
df = excel_service.read_excel("mi_archivo_de_entrada.xlsx")
print("Datos cargados exitosamente.")
# ... procesar el DataFrame ...
# Guardar el DataFrame con formato personalizado
format_options = {
'freeze_row': 2,
'header_color': 'E6E6E6'
}
excel_service.save_excel(
df,
"mi_archivo_de_salida.xlsx",
sheet_name="Resultados",
format_options=format_options
)
print("Archivo guardado con éxito.")
except Exception as e:
print(f"Ocurrió un error al manejar el archivo Excel: {e}")
if __name__ == "__main__":
main()
```
### 8.2 Servicios de Lenguaje
Los servicios de lenguaje (`services/language/`) permiten detectar el idioma de un texto.
**Ejemplo de importación y uso:**
```python
# Asegúrate de tener el path raíz configurado
# ... (código de configuración de sys.path)
from services.language.language_factory import LanguageFactory
from services.language.language_utils import LanguageUtils
def main():
# Crear el servicio de detección de idioma
allowed_languages = LanguageUtils.get_available_languages()
detector = LanguageFactory.create_service("langid", allowed_languages=allowed_languages)
# Detectar idioma de un texto
text = "Este es un texto de ejemplo en español."
lang, confidence = detector.detect_language(text)
print(f"Texto: '{text}'")
print(f"Idioma detectado: {LanguageUtils.get_language_name(lang)} (código: {lang})")
print(f"Confianza: {confidence:.2f}")
if __name__ == "__main__":
main()
```
### 8.3 Servicios de LLM (Modelos de Lenguaje Grandes)
El proyecto integra una fábrica de servicios (`LLMFactory`) para interactuar con diferentes Modelos de Lenguaje Grandes (LLMs). Esto permite a los scripts aprovechar la IA generativa para tareas como el análisis de código, la generación de descripciones semánticas, etc.
#### 8.3.1 Configuración de API Keys
La mayoría de los servicios de LLM requieren una clave de API para funcionar. El sistema gestiona esto de forma centralizada a través de variables de entorno.
1. **Crear el archivo `.env`**: En el **directorio raíz del proyecto**, crea un archivo llamado `.env` (si aún no existe).
2. **Añadir las API Keys**: Abre el archivo `.env` y añade las claves para los servicios que planeas utilizar. El sistema cargará estas variables automáticamente al inicio.
```env
# Ejemplo de contenido para el archivo .env
# (Solo necesitas añadir las claves de los servicios que vayas a usar)
OPENAI_API_KEY="sk-..."
GROQ_API_KEY="gsk_..."
CLAUDE_API_KEY="sk-ant-..."
GEMINI_API_KEY="AIzaSy..."
GROK_API_KEY="TU_API_KEY_DE_GROK"
```
**Nota**: El servicio `ollama` se ejecuta localmente y no requiere una clave de API.
#### 8.3.2 Ejemplo de importación y uso
El siguiente ejemplo muestra cómo un script puede cargar su configuración, inicializar un servicio de LLM y usarlo para generar texto. Este patrón es similar al utilizado en `x3_generate_semantic_descriptions.py`.
```python
# Asegúrate de tener el path raíz configurado
# ... (código de configuración de sys.path)
from services.llm.llm_factory import LLMFactory
from backend.script_utils import load_configuration
def main():
# Cargar la configuración del script, que puede incluir qué LLM usar
configs = load_configuration()
llm_configs = configs.get("llm", {})
# Obtener el tipo de servicio y otros parámetros del config
# Por defecto, usamos 'groq' si no se especifica
service_type = llm_configs.get("service", "groq")
print(f"🤖 Inicializando servicio LLM: {service_type}")
# Crear una instancia del servicio usando la fábrica
# La fábrica se encarga de pasar las API keys desde las variables de entorno
llm_service = LLMFactory.create_service(service_type, **llm_configs)
if not llm_service:
print(f"❌ Error: No se pudo crear el servicio LLM '{service_type}'. Abortando.")
return
# Usar el servicio para generar texto
try:
prompt = "Explica la computación cuántica en una sola frase."
print(f"Enviando prompt: '{prompt}'")
description = llm_service.generate_text(prompt)
print("\nRespuesta del LLM:")
print(description)
except Exception as e:
print(f"Ocurrió un error al contactar al servicio LLM: {e}")
if __name__ == "__main__":
main()
```
#### 8.3.3 Servicios Disponibles
La `LLMFactory` soporta los siguientes tipos de servicio (`service_type`):
- `openai`
- `groq`
- `claude`
- `gemini`
- `grok`
- `ollama` (para ejecución local)

85
app.py
View File

@ -10,7 +10,8 @@ from datetime import datetime
import time # Added for shutdown delay
import sys # Added for platform detection
import subprocess # Add this to the imports at the top
import shutil # For shutil.whichimport os
import shutil # For shutil.which
import glob # For finding workspace files
# --- Imports for System Tray Icon ---
import threading
@ -993,6 +994,29 @@ def get_all_csharp_executables(project_id):
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/csharp-solution-file/<project_id>", methods=["GET"])
def get_csharp_solution_file(project_id):
"""Obtener archivo de solución (.sln) del proyecto C#"""
try:
solution_file = csharp_launcher_manager.find_solution_file(project_id)
if solution_file:
return jsonify({
"status": "success",
"solution_file": solution_file,
"exists": True
})
else:
return jsonify({
"status": "success",
"solution_file": None,
"exists": False
})
except Exception as e:
return jsonify({
"status": "error",
"message": f"Error buscando archivo de solución: {str(e)}"
}), 500
# === FIN C# LAUNCHER APIs ===
@ -1188,7 +1212,43 @@ def get_python_markdown_content(project_id, relative_path):
# === FIN PYTHON LAUNCHER APIs ===
# --- Helper function to find VS Code ---
# --- Helper functions ---
def find_workspace_file(directory, editor):
"""
Busca archivos de workspace específicos en un directorio.
Args:
directory (str): Directorio donde buscar
editor (str): Editor ('vscode' o 'cursor')
Returns:
str: Ruta del archivo de workspace encontrado, o None si no hay
"""
if not os.path.isdir(directory):
return None
# Definir extensiones de archivo según el editor
if editor == 'vscode':
extensions = ['.code-workspace']
elif editor == 'cursor':
# Cursor puede usar tanto .cursor-workspace como .code-workspace
extensions = ['.cursor-workspace', '.code-workspace']
else:
return None
# Buscar archivos con las extensiones apropiadas
for ext in extensions:
pattern = os.path.join(directory, f"*{ext}")
workspace_files = glob.glob(pattern)
if workspace_files:
# Si hay múltiples, tomar el primero (o se podría implementar lógica más sofisticada)
workspace_file = workspace_files[0]
print(f"Found {editor} workspace: {workspace_file}")
return workspace_file
return None
def find_vscode_executable():
"""Intenta encontrar el ejecutable de VS Code en ubicaciones comunes y en el PATH."""
# Comprobar la variable de entorno VSCODE_PATH primero (si la defines)
@ -1332,13 +1392,30 @@ def open_group_in_editor(editor, group_system, group_id):
print(f"Launching {editor_name} from: {editor_path}")
print(f"Opening directory: {script_group_path}")
# Buscar archivos de workspace específicos o archivos de solución
target_to_open = script_group_path
# Para VSCode y Cursor, buscar archivos de workspace
if editor in ['vscode', 'cursor']:
workspace_file = find_workspace_file(script_group_path, editor)
if workspace_file:
target_to_open = workspace_file
print(f"Found {editor} workspace file: {workspace_file}")
# Para Visual Studio 2022 y proyectos C#, intentar abrir archivo .sln específico
elif editor == 'vs2022' and group_system == 'csharp':
solution_file = csharp_launcher_manager.find_solution_file(group_id)
if solution_file:
target_to_open = solution_file
print(f"Found solution file: {solution_file}")
# Ejecutar el editor
process = subprocess.Popen(f'"{editor_path}" "{script_group_path}"', shell=True)
process = subprocess.Popen(f'"{editor_path}" "{target_to_open}"', shell=True)
print(f"{editor_name} process started with PID: {process.pid}")
return jsonify({
"status": "success",
"message": f"{editor_name} abierto en: {script_group_path}"
"message": f"{editor_name} abierto en: {target_to_open}"
})
except Exception as e:

View File

@ -0,0 +1 @@
(* Código SCL generado desde LAD TwinCAT *)\n(* Convertidor mejorado con SymPy - Estructura DNF preferida *)\n(* Path original: *)\n\nPROGRAM \n\n (* Código LAD convertido a SCL *)\n\n\nEND_PROGRAM

File diff suppressed because it is too large Load Diff

View File

@ -2,220 +2,132 @@
## Introducción
Esta guía explica cómo configurar y usar correctamente la función `load_configuration()` en scripts ubicados bajo el directorio `/backend`. La función carga configuraciones desde un archivo `script_config.json` ubicado en el mismo directorio que el script que la llama.
Esta guía explica cómo usar la función `load_configuration()` para cargar parámetros desde un archivo `script_config.json` en los scripts del backend.
## Configuración del Path e Importación
## 1. Configuración del Script
### 1. Configuración estándar del Path
Para que tus scripts puedan encontrar los módulos del proyecto, necesitas añadir el directorio raíz al path de Python.
Para scripts ubicados en subdirectorios bajo `/backend`, usa este patrón estándar:
### Path Setup e Importación
Coloca este código al inicio de tu script:
```python
import os
import sys
# Configurar el path al directorio raíz del proyecto
# Añadir el directorio raíz al sys.path
# El número de `os.path.dirname()` depende de la profundidad del script.
# Para /backend/script_groups/grupo/script.py, son 4.
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
# Importar la función de configuración
# Importar la función
from backend.script_utils import load_configuration
```
**Nota:** El número de `os.path.dirname()` anidados depende de la profundidad del script:
- Scripts en `/backend/script_groups/grupo/`: 4 niveles
- Scripts en `/backend/`: 2 niveles
## 2. Cargar la Configuración
### 2. Importación Correcta
La función `load_configuration()` busca y carga un archivo llamado `script_config.json` que debe estar en el **mismo directorio** que el script que la ejecuta. Devuelve un diccionario con todo el contenido del JSON.
**✅ Correcto:**
```python
from backend.script_utils import load_configuration
```
**❌ Incorrecto:**
```python
from script_utils import load_configuration # No funciona desde subdirectorios
```
## Uso de la Función load_configuration()
### Implementación Básica
### Ejemplo de Uso
```python
def main():
# Cargar configuraciones
# Cargar configuraciones del archivo script_config.json
configs = load_configuration()
# Obtener el directorio de trabajo
working_directory = configs.get("working_directory", "")
# Es buena práctica verificar si la configuración se cargó
if not configs:
print("Error: No se pudo cargar la configuración. Saliendo.")
return
# Acceder a configuraciones por nivel
# Acceder a los parámetros usando .get() para evitar errores
working_directory = configs.get("working_directory", "")
level1_config = configs.get("level1", {})
level2_config = configs.get("level2", {})
level3_config = configs.get("level3", {})
# Ejemplo de uso de parámetros específicos con valores por defecto
# Ejemplo de uso de un parámetro específico con valor por defecto
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
print(f"Directorio de trabajo: {working_directory}")
print(f"Directorio de salida SCL: {scl_output_dir}")
if __name__ == "__main__":
main()
```
### Estructura del Archivo script_config.json
## 3. Archivo `script_config.json`
El archivo `script_config.json` debe estar ubicado en el mismo directorio que el script que llama a `load_configuration()`. Estructura recomendada:
Este archivo contiene los parámetros de tu script. La estructura interna del JSON, como el uso de `"level1"`, `"level2"`, etc., es una convención para organizar los parámetros. `load_configuration()` simplemente lee el archivo y devuelve su contenido.
**Ejemplo de `script_config.json`:**
```json
{
"working_directory": "/ruta/al/directorio/de/trabajo",
"level1": {
"parametro_global_1": "valor1",
"parametro_global_2": "valor2"
"parametro_global_1": "valor1"
},
"level2": {
"scl_output_dir": "scl_output",
"xref_output_dir": "xref_output",
"xref_source_subdir": "source",
"aggregated_filename": "full_project_representation.md"
"xref_output_dir": "xref_output"
},
"level3": {
"parametro_especifico_1": true,
"parametro_especifico_2": 100
"parametro_especifico_1": true
}
}
```
## Ejemplo Completo de Implementación
## 4. Manejo de Errores
```python
"""
Script de ejemplo que demuestra el uso completo de load_configuration()
"""
`load_configuration()` está diseñada para ser robusta:
- Si `script_config.json` no se encuentra, retorna un diccionario vacío `{}`.
- Si el JSON es inválido, imprime un error y también retorna `{}`.
import os
import sys
import json
**Siempre** comprueba si el diccionario devuelto está vacío para manejar estos casos de forma segura en tu script.
# Configuración del path
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
from backend.script_utils import load_configuration
## 5. Jerarquía de Archivos de Configuración
El sistema utiliza un modelo de configuración en cascada para gestionar los parámetros de los scripts. Esta jerarquía permite establecer configuraciones a nivel global, de grupo y de trabajo. Antes de la ejecución, el launcher lee estos archivos, los combina y genera un único `script_config.json` en la carpeta del script. La función `load_configuration()` es la que finalmente lee este archivo consolidado.
def main():
print("=== Cargando Configuración ===")
A continuación se describe la finalidad y ubicación de cada archivo clave.
# Cargar configuraciones
configs = load_configuration()
### Archivos de Valores (Parámetros)
# Verificar que se cargó correctamente
if not configs:
print("Error: No se pudo cargar la configuración")
return
Contienen los datos y variables que utilizará el script. La configuración se superpone en el siguiente orden: `Nivel 1 < Nivel 2 < Nivel 3`.
# Obtener configuraciones
working_directory = configs.get("working_directory", "")
level1_config = configs.get("level1", {})
level2_config = configs.get("level2", {})
level3_config = configs.get("level3", {})
- **`data.json` (Nivel 1 - Global)**
- **Ubicación:** `data/data.json`
- **Utilidad:** Almacena variables globales disponibles para todos los scripts. Ideal para parámetros generales.
- **Acceso:** Sus datos se cargan en la clave `"level1"` del diccionario `configs`.
# Mostrar configuraciones cargadas
print(f"Directorio de trabajo: {working_directory}")
print("Configuración Nivel 1:", json.dumps(level1_config, indent=2))
print("Configuración Nivel 2:", json.dumps(level2_config, indent=2))
print("Configuración Nivel 3:", json.dumps(level3_config, indent=2))
- **`script_config.json` (Nivel 2 - Grupo)**
- **Ubicación:** En la raíz de cada directorio de grupo (ej: `backend/script_groups/MiGrupo/script_config.json`).
- **Utilidad:** Define parámetros compartidos por todos los scripts de un grupo.
- **Acceso:** Sus datos se cargan en la clave `"level2"`.
# Ejemplo de uso de parámetros con valores por defecto
scl_output_dir = level2_config.get("scl_output_dir", "scl_output")
xref_output_dir = level2_config.get("xref_output_dir", "xref_output")
- **`work_dir.json` (Nivel 3 - Directorio de Trabajo)**
- **Ubicación:** Dentro del directorio de trabajo que el script va a procesar.
- **Utilidad:** Contiene parámetros para una ejecución específica. Es el nivel más específico y sobrescribe los anteriores.
- **Acceso:** Sus datos se cargan en la clave `"level3"`.
print(f"Directorio de salida SCL: {scl_output_dir}")
print(f"Directorio de salida XREF: {xref_output_dir}")
### Archivos de Esquema (Definiciones de Estructura)
No contienen valores, sino que describen la estructura y tipos de datos que los archivos de valores deben tener. Son usados por el launcher para validación y para generar interfaces de configuración.
if __name__ == "__main__":
main()
```
- **`esquema_group.json`**
- **Ubicación:** Raíz del directorio del grupo.
- **Utilidad:** Define la estructura del `script_config.json` del grupo.
## Manejo de Errores
- **`esquema_work.json`**
- **Ubicación:** Raíz del directorio del grupo.
- **Utilidad:** Define la estructura del `work_dir.json`.
La función `load_configuration()` maneja automáticamente los siguientes casos:
1. **Archivo no encontrado**: Retorna un diccionario vacío `{}`
2. **JSON inválido**: Retorna un diccionario vacío y muestra un mensaje de error
3. **Errores de lectura**: Retorna un diccionario vacío y muestra un mensaje de error
### Verificación de Configuración Válida
```python
configs = load_configuration()
# Verificar que se cargó correctamente
if not configs:
print("Advertencia: No se pudo cargar la configuración, usando valores por defecto")
working_directory = "."
else:
working_directory = configs.get("working_directory", ".")
# Verificar directorio de trabajo
if not os.path.exists(working_directory):
print(f"Error: El directorio de trabajo no existe: {working_directory}")
return
```
## Mejores Prácticas
1. **Siempre proporciona valores por defecto** al usar `.get()`:
```python
valor = config.get("clave", "valor_por_defecto")
```
2. **Verifica la existencia de directorios críticos**:
```python
if not os.path.exists(working_directory):
print(f"Error: Directorio no encontrado: {working_directory}")
return
```
3. **Documenta los parámetros esperados** en tu script:
```python
# Parámetros esperados en level2:
# - scl_output_dir: Directorio de salida para archivos SCL
# - xref_output_dir: Directorio de salida para referencias cruzadas
```
4. **Usa nombres de parámetros consistentes** en todos los scripts del mismo grupo.
## Definición Técnica de load_configuration()
```python
def load_configuration() -> Dict[str, Any]:
"""
Load configuration from script_config.json in the current script directory.
Returns:
Dict containing configurations with levels 1, 2, 3 and working_directory
Example usage in scripts:
from backend.script_utils import load_configuration
configs = load_configuration()
level1_config = configs.get("level1", {})
level2_config = configs.get("level2", {})
level3_config = configs.get("level3", {})
working_dir = configs.get("working_directory", "")
"""
```
La función utiliza `inspect.stack()` para determinar automáticamente el directorio del script que la llama, asegurando que siempre busque el archivo `script_config.json` en la ubicación correcta.
## Documentación de Scripts para el Launcher
## 6. Documentación de Scripts para el Launcher
El sistema de launcher utiliza archivos JSON para mostrar información sobre los grupos de scripts y scripts individuales en la interfaz web.
@ -291,3 +203,126 @@ Para un grupo "XML Parser to SCL":
}
}
```
## 7. Requisitos de Codificación de Salida (stdout)
Toda la salida estándar (`stdout`) generada por los scripts (por ejemplo, mediante la función `print()`) es capturada en tiempo real y mostrada en el panel de logs del frontend.
Para garantizar que el texto se muestre correctamente y evitar caracteres corruptos (mojibake), **la salida de los scripts debe tener codificación UTF-8**.
### Configuración Automática
El sistema está diseñado para facilitar esto. Al ejecutar un script, el entorno se configura automáticamente para que la salida de Python sea UTF-8. Específicamente, se establece la variable de entorno `PYTHONIOENCODING=utf-8`.
Gracias a esto, en la mayoría de los casos, **no necesitas hacer nada especial**. Simplemente usa `print()` y la codificación será la correcta.
### Casos Especiales y Solución de Problemas
Pueden surgir problemas si tu script lee datos de fuentes externas (como archivos) que tienen una codificación diferente. Si imprimes contenido directamente sin decodificarlo primero, podrías enviar bytes no válidos a la salida.
La regla es: **decodifica los datos de entrada a un string de Python lo antes posible, y deja que `print()` se encargue de la codificación de salida.**
**Ejemplo de cómo manejar un archivo con codificación `latin-1`:**
```python
# INCORRECTO: Si el archivo no es UTF-8, esto puede enviar bytes inválidos.
# El launcher intentará decodificarlos como UTF-8 y podría fallar o mostrar basura.
try:
with open('mi_archivo_legacy.txt', 'rb') as f:
print(f.read())
except Exception as e:
print(f"Esto probablemente falle o muestre texto corrupto: {e}")
# CORRECTO: Leer el archivo especificando su codificación para decodificarlo a un string.
# Una vez que es un string de Python, `print` lo manejará correctamente.
try:
with open('mi_archivo_legacy.txt', 'r', encoding='latin-1') as f:
contenido = f.read()
# Ahora `contenido` es un string de Python.
# print() lo codificará a UTF-8 automáticamente gracias a la configuración del entorno.
print(contenido)
except FileNotFoundError:
print("El archivo 'mi_archivo_legacy.txt' no fue encontrado para el ejemplo.")
except Exception as e:
print(f"Error al procesar el archivo: {e}")
```
## 8. Uso de Servicios Compartidos
El proyecto ofrece una serie de servicios reutilizables en el directorio `services/` para tareas comunes como la manipulación de archivos Excel, detección de idioma o traducción.
Para utilizar estos servicios en tu script, asegúrate de que el directorio raíz del proyecto esté en el `sys.path`, como se explica en la sección 1 de esta guía.
### 8.1 Servicio de Excel (`ExcelService`)
El `ExcelService` (`services/excel/excel_service.py`) facilita la lectura y escritura de archivos Excel, con manejo de reintentos (por si el archivo está abierto) y opciones de formato.
**Ejemplo de importación y uso:**
```python
# Asegúrate de tener el path raíz configurado
# ... (código de configuración de sys.path)
from services.excel.excel_service import ExcelService
def main():
excel_service = ExcelService()
# Leer un archivo Excel
try:
df = excel_service.read_excel("mi_archivo_de_entrada.xlsx")
print("Datos cargados exitosamente.")
# ... procesar el DataFrame ...
# Guardar el DataFrame con formato personalizado
format_options = {
'freeze_row': 2,
'header_color': 'E6E6E6'
}
excel_service.save_excel(
df,
"mi_archivo_de_salida.xlsx",
sheet_name="Resultados",
format_options=format_options
)
print("Archivo guardado con éxito.")
except Exception as e:
print(f"Ocurrió un error al manejar el archivo Excel: {e}")
if __name__ == "__main__":
main()
```
### 8.2 Servicios de Lenguaje
Los servicios de lenguaje (`services/language/`) permiten detectar el idioma de un texto.
**Ejemplo de importación y uso:**
```python
# Asegúrate de tener el path raíz configurado
# ... (código de configuración de sys.path)
from services.language.language_factory import LanguageFactory
from services.language.language_utils import LanguageUtils
def main():
# Crear el servicio de detección de idioma
allowed_languages = LanguageUtils.get_available_languages()
detector = LanguageFactory.create_service("langid", allowed_languages=allowed_languages)
# Detectar idioma de un texto
text = "Este es un texto de ejemplo en español."
lang, confidence = detector.detect_language(text)
print(f"Texto: '{text}'")
print(f"Idioma detectado: {LanguageUtils.get_language_name(lang)} (código: {lang})")
print(f"Confianza: {confidence:.2f}")
if __name__ == "__main__":
main()
```

View File

@ -5,6 +5,9 @@
},
{
"path": "C:/Trabajo/SIDEL/13 - E5.007560 - Modifica O&U - SAE235/Reporte/Analisis"
},
{
"path": "C:/Trabajo/SIDEL/13 - E5.007560 - Modifica O&U - SAE235/Reporte/ExportTwinCat"
}
],
"settings": {}

View File

@ -1,172 +0,0 @@
# Convertidor LAD a Pseudocódigo Estructurado
## Descripción
Este proyecto proporciona herramientas para convertir código LAD (Ladder Diagram) de TwinCAT a pseudocódigo estructurado compatible con IEC61131-3. El convertidor mantiene la semántica original del código LAD mientras lo transforma a un formato más legible y estructurado.
## Características
- ✅ **Parsing completo de LAD**: Analiza la estructura completa de archivos `.EXP` de TwinCAT
- ✅ **Conversión semántica**: Mantiene la lógica original del diagrama ladder
- ✅ **Formato estructurado**: Genera código pseudo estructurado con IF-THEN-END_IF
- ✅ **Manejo de contactos**: Convierte contactos normales y negados correctamente
- ✅ **Function blocks**: Identifica y convierte llamadas a bloques de función
- ✅ **Operadores matemáticos**: Maneja operaciones aritméticas y lógicas
- ✅ **Comentarios**: Preserva comentarios de las redes originales
## Archivos del Proyecto
### Convertidores Principales
1. **`simple_lad_converter.py`** - Convertidor simplificado y robusto (recomendado)
2. **`lad_to_pseudocode_converter.py`** - Convertidor básico inicial
3. **`lad_to_pseudocode_converter_enhanced.py`** - Versión avanzada con más características
### Archivos de Prueba
- **`test_simple.py`** - Script de prueba para el convertidor simple
- **`ejemplo_conversion.py`** - Ejemplo de uso del convertidor básico
- **`test_enhanced_converter.py`** - Prueba para el convertidor avanzado
## Uso Rápido
### Método Simple (Recomendado)
```bash
python test_simple.py
```
Este comando procesará el archivo `.example/INPUT.EXP` y generará `output_simple.txt` con el código convertido.
### Uso Directo del Convertidor
```python
from simple_lad_converter import SimpleLadConverter
converter = SimpleLadConverter()
converter.parse_file("mi_archivo.EXP")
structured_code = converter.save_to_file("salida.txt")
```
### Línea de Comandos
```bash
python lad_to_pseudocode_converter.py archivo_entrada.EXP archivo_salida.txt
```
## Estructura del Código LAD Soportada
El convertidor puede procesar las siguientes estructuras de TwinCAT:
### Elementos LAD Reconocidos
- `_NETWORK` - Inicio de red
- `_LD_ASSIGN` - Asignaciones
- `_LD_CONTACT` - Contactos (entradas)
- `_LD_AND` / `_LD_OR` - Operaciones lógicas
- `_FUNCTIONBLOCK` - Bloques de función
- `_OPERATOR` - Operadores matemáticos
- `_COMMENT` / `_END_COMMENT` - Comentarios
- `_OUTPUT` - Variables de salida
### Operadores Soportados
- **Aritméticos**: ADD, SUB, MUL, DIV
- **Lógicos**: AND, OR
- **Comparación**: LT, GT, EQ
- **Especiales**: SEL, MOVE
## Ejemplo de Conversión
### Código LAD Original
```
_NETWORK
_COMMENT
Verificación de presión CO2
_END_COMMENT
_LD_ASSIGN
_LD_CONTACT
DI_Air_InletPress_OK
_NEGATIV
_FUNCTIONBLOCK
mAirPressOk
_OUTPUT
gInLinePressAirOk
```
### Código Estructurado Generado
```
// Red 5
// Verificación de presión CO2
IF NOT DI_Air_InletPress_OK THEN
gInLinePressAirOk := mAirPressOk();
END_IF;
```
## Estructura del Archivo de Salida
El código generado sigue esta estructura:
```
// Código pseudo estructurado generado desde LAD TwinCAT
// Compatible con IEC61131-3
PROGRAM Input_Converted
// Red 1
IF condicion1 AND condicion2 THEN
variable_salida := funcion_bloque(parametros);
END_IF;
// Red 2
variable := operando1 ADD operando2;
// Red N...
END_PROGRAM
```
## Resultados de la Conversión
El convertidor ha procesado exitosamente el archivo `INPUT.EXP` que contiene:
- **86 redes LAD** en total
- **Múltiples tipos de elementos**: contactos, function blocks, operadores
- **Preservación de comentarios** originales
- **Conversión correcta de lógica condicional** con IF-THEN-END_IF
### Estadísticas del Ejemplo
- Archivo de entrada: `.example/INPUT.EXP` (4,611 líneas)
- Redes procesadas: 86
- Archivo de salida: ~235 líneas de código estructurado
- Reducción de complejidad: ~95%
## Ventajas del Código Convertido
1. **Legibilidad**: Más fácil de leer que el formato LAD textual
2. **Mantenibilidad**: Estructura clara con comentarios preservados
3. **Debugging**: Lógica condicional explícita
4. **Documentación**: Comentarios de red integrados
5. **Portabilidad**: Formato pseudo-código universal
## Limitaciones Conocidas
- Algunos parámetros internos pueden aparecer como `_POSITIV`, `_NEGATIV`
- Estructuras complejas de LAD pueden requerir revisión manual
- El convertidor es específico para el formato de TwinCAT
## Desarrollo Futuro
- [ ] Mejorar el parsing de parámetros de function blocks
- [ ] Añadir soporte para más tipos de operadores
- [ ] Implementar validación de sintaxis
- [ ] Crear interfaz gráfica para conversión
- [ ] Soporte para otros formatos de PLC
## Contribuciones
Este convertidor fue desarrollado para facilitar el análisis y mantenimiento de código LAD en proyectos de automatización industrial. Las contribuciones y mejoras son bienvenidas.
---
**Nota**: Este es un proyecto de código abierto para ayudar en la migración y análisis de código LAD de TwinCAT a formatos más estructurados.

File diff suppressed because it is too large Load Diff

View File

@ -1,463 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script para generar documentación de adaptación de IOs
entre TwinCAT y TIA Portal - Proyecto SIDEL
Autor: Generado automáticamente
Proyecto: E5.007560 - Modifica O&U - SAE235
"""
import re
import os
import sys
import pandas as pd
import json
from pathlib import Path
from typing import Dict, List, Tuple, Optional
import argparse
from collections import defaultdict
# Configurar el path al directorio raíz del proyecto
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
# Importar la función de configuración
from backend.script_utils import load_configuration
def load_tiaportal_adaptations(working_directory, file_path='IO Adapted.md'):
"""Carga las adaptaciones de TIA Portal desde el archivo markdown"""
full_file_path = os.path.join(working_directory, file_path)
print(f"Cargando adaptaciones de TIA Portal desde: {full_file_path}")
adaptations = {}
if not os.path.exists(full_file_path):
print(f"⚠️ Archivo {full_file_path} no encontrado")
return adaptations
with open(full_file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Patrones mejorados para diferentes tipos de IOs
patterns = [
# Digitales: E0.0, A0.0
r'\|\s*([EA]\d+\.\d+)\s*\|\s*([^|]+?)\s*\|',
# Analógicos: PEW100, PAW100
r'\|\s*(P[EA]W\d+)\s*\|\s*([^|]+?)\s*\|',
# Profibus: EW 1640, AW 1640
r'\|\s*([EA]W\s+\d+)\s*\|\s*([^|]+?)\s*\|'
]
for pattern in patterns:
matches = re.findall(pattern, content, re.MULTILINE)
for io_addr, master_tag in matches:
io_addr = io_addr.strip()
master_tag = master_tag.strip()
if io_addr and master_tag and not master_tag.startswith('-'):
adaptations[io_addr] = master_tag
print(f" 📍 {io_addr}{master_tag}")
print(f"✅ Cargadas {len(adaptations)} adaptaciones de TIA Portal")
return adaptations
def scan_twincat_definitions(working_directory, directory='TwinCat'):
"""Escanea archivos TwinCAT para encontrar definiciones de variables AT %"""
full_directory = os.path.join(working_directory, directory)
print(f"\n🔍 Escaneando definiciones TwinCAT en: {full_directory}")
definitions = {}
if not os.path.exists(full_directory):
print(f"⚠️ Directorio {full_directory} no encontrado")
return definitions
# Patrones para definiciones AT %
definition_patterns = [
r'(\w+)\s+AT\s+%([IQ][XWB]\d+(?:\.\d+)?)\s*:\s*(\w+);', # Activas
r'(\w+)\s+\(\*\s*AT\s+%([IQ][XWB]\d+(?:\.\d+)?)\s*\*\)\s*:\s*(\w+);', # Comentadas
]
for file_path in Path(full_directory).glob('*.scl'):
print(f" 📄 Procesando: {file_path.name}")
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
for pattern in definition_patterns:
matches = re.findall(pattern, content, re.MULTILINE | re.IGNORECASE)
for var_name, io_addr, data_type in matches:
var_name = var_name.strip()
io_addr = io_addr.strip()
data_type = data_type.strip()
definitions[var_name] = {
'address': io_addr,
'type': data_type,
'file': file_path.name,
'definition_line': content[:content.find(var_name)].count('\n') + 1
}
print(f" 🔗 {var_name} AT %{io_addr} : {data_type}")
print(f"✅ Encontradas {len(definitions)} definiciones TwinCAT")
return definitions
def scan_twincat_usage(working_directory, directory='TwinCat'):
"""Escanea archivos TwinCAT para encontrar uso de variables"""
full_directory = os.path.join(working_directory, directory)
print(f"\n🔍 Escaneando uso de variables TwinCAT en: {full_directory}")
usage_data = defaultdict(list)
if not os.path.exists(full_directory):
print(f"⚠️ Directorio {full_directory} no encontrado")
return usage_data
for file_path in Path(full_directory).glob('*.scl'):
print(f" 📄 Analizando uso en: {file_path.name}")
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
for line_num, line in enumerate(lines, 1):
# Buscar variables que empiecen con DI_, DO_, AI_, AO_
var_matches = re.findall(r'\b([DA][IO]_\w+)\b', line)
for var_name in var_matches:
usage_data[var_name].append({
'file': file_path.name,
'line': line_num,
'context': line.strip()[:100] + ('...' if len(line.strip()) > 100 else '')
})
print(f"✅ Encontrado uso de {len(usage_data)} variables diferentes")
return usage_data
def convert_tia_to_twincat(tia_addr):
"""Convierte direcciones TIA Portal a formato TwinCAT"""
conversions = []
# Digitales
if re.match(r'^E\d+\.\d+$', tia_addr): # E0.0 → IX0.0
twincat_addr = tia_addr.replace('E', 'IX')
conversions.append(twincat_addr)
elif re.match(r'^A\d+\.\d+$', tia_addr): # A0.0 → QX0.0
twincat_addr = tia_addr.replace('A', 'QX')
conversions.append(twincat_addr)
# Analógicos
elif re.match(r'^PEW\d+$', tia_addr): # PEW100 → IW100
twincat_addr = tia_addr.replace('PEW', 'IW')
conversions.append(twincat_addr)
elif re.match(r'^PAW\d+$', tia_addr): # PAW100 → QW100
twincat_addr = tia_addr.replace('PAW', 'QW')
conversions.append(twincat_addr)
# Profibus
elif re.match(r'^EW\s+\d+$', tia_addr): # EW 1234 → IB1234
addr_num = re.search(r'\d+', tia_addr).group()
conversions.append(f'IB{addr_num}')
elif re.match(r'^AW\s+\d+$', tia_addr): # AW 1234 → QB1234
addr_num = re.search(r'\d+', tia_addr).group()
conversions.append(f'QB{addr_num}')
return conversions
def find_variable_by_address(definitions, target_address):
"""Busca variable por dirección exacta"""
for var_name, info in definitions.items():
if info['address'] == target_address:
return var_name, info
return None, None
def find_variable_by_name_similarity(definitions, usage_data, master_tag):
"""Busca variables por similitud de nombre"""
candidates = []
# Limpiar el master tag para comparación
clean_master = re.sub(r'^[DA][IO]_', '', master_tag).lower()
# Buscar en definiciones
for var_name, info in definitions.items():
clean_var = re.sub(r'^[DA][IO]_', '', var_name).lower()
if clean_master in clean_var or clean_var in clean_master:
candidates.append((var_name, info, 'definition'))
# Buscar en uso
for var_name in usage_data.keys():
clean_var = re.sub(r'^[DA][IO]_', '', var_name).lower()
if clean_master in clean_var or clean_var in clean_master:
# Intentar encontrar la definición de esta variable
var_info = definitions.get(var_name)
if not var_info:
var_info = {'address': 'Unknown', 'type': 'Unknown', 'file': 'Not found'}
candidates.append((var_name, var_info, 'usage'))
return candidates
def analyze_adaptations(tia_adaptations, twincat_definitions, twincat_usage):
"""Analiza las correlaciones entre TIA Portal y TwinCAT"""
print(f"\n📊 Analizando correlaciones...")
results = []
matches_found = 0
for tia_addr, master_tag in tia_adaptations.items():
result = {
'tia_address': tia_addr,
'master_tag': master_tag,
'twincat_variable': None,
'twincat_address': None,
'twincat_type': None,
'match_type': None,
'definition_file': None,
'usage_files': [],
'usage_count': 0,
'confidence': 'Low'
}
# 1. Buscar por conversión directa de dirección
twincat_addresses = convert_tia_to_twincat(tia_addr)
var_found = False
for twincat_addr in twincat_addresses:
var_name, var_info = find_variable_by_address(twincat_definitions, twincat_addr)
if var_name:
result.update({
'twincat_variable': var_name,
'twincat_address': var_info['address'],
'twincat_type': var_info['type'],
'match_type': 'Address Match',
'definition_file': var_info['file'],
'confidence': 'High'
})
var_found = True
matches_found += 1
break
# 2. Si no se encontró por dirección, buscar por nombre
if not var_found:
candidates = find_variable_by_name_similarity(twincat_definitions, twincat_usage, master_tag)
if candidates:
# Tomar el mejor candidato
best_candidate = candidates[0]
var_name, var_info, source = best_candidate
result.update({
'twincat_variable': var_name,
'twincat_address': var_info.get('address', 'Unknown'),
'twincat_type': var_info.get('type', 'Unknown'),
'match_type': f'Name Similarity ({source})',
'definition_file': var_info.get('file', 'Unknown'),
'confidence': 'Medium'
})
matches_found += 1
# 3. Buscar información de uso
if result['twincat_variable']:
var_name = result['twincat_variable']
if var_name in twincat_usage:
usage_info = twincat_usage[var_name]
result['usage_files'] = list(set([u['file'] for u in usage_info]))
result['usage_count'] = len(usage_info)
results.append(result)
# Log del progreso
status = "" if result['twincat_variable'] else ""
print(f" {status} {tia_addr}{master_tag}")
if result['twincat_variable']:
print(f" 🔗 {result['twincat_variable']} AT %{result['twincat_address']}")
if result['usage_count'] > 0:
print(f" 📝 Usado en {result['usage_count']} lugares: {', '.join(result['usage_files'])}")
print(f"\n🎯 Resumen: {matches_found}/{len(tia_adaptations)} variables correlacionadas ({matches_found/len(tia_adaptations)*100:.1f}%)")
return results
def create_results_directory(working_directory):
"""Crea el directorio de resultados si no existe"""
results_dir = Path(working_directory) / 'resultados'
results_dir.mkdir(exist_ok=True)
print(f"📁 Directorio de resultados: {results_dir.absolute()}")
return results_dir
def generate_json_output(results, working_directory, output_file='io_adaptation_data.json'):
"""Genera archivo JSON con datos estructurados para análisis posterior"""
full_output_file = os.path.join(working_directory, 'resultados', output_file)
print(f"\n📄 Generando archivo JSON: {full_output_file}")
json_data = {
"metadata": {
"generated_at": pd.Timestamp.now().isoformat(),
"project": "E5.007560 - Modifica O&U - SAE235",
"total_adaptations": len(results),
"matched_variables": len([r for r in results if r['twincat_variable']]),
"high_confidence": len([r for r in results if r['confidence'] == 'High']),
"medium_confidence": len([r for r in results if r['confidence'] == 'Medium'])
},
"adaptations": []
}
for result in results:
adaptation = {
"tia_portal": {
"address": result['tia_address'],
"tag": result['master_tag']
},
"twincat": {
"variable": result['twincat_variable'],
"address": result['twincat_address'],
"data_type": result['twincat_type'],
"definition_file": result['definition_file']
},
"correlation": {
"match_type": result['match_type'],
"confidence": result['confidence'],
"found": result['twincat_variable'] is not None
},
"usage": {
"usage_count": result['usage_count'],
"usage_files": result['usage_files']
}
}
json_data["adaptations"].append(adaptation)
with open(full_output_file, 'w', encoding='utf-8') as f:
json.dump(json_data, f, indent=2, ensure_ascii=False)
print(f"✅ Archivo JSON generado: {full_output_file}")
def generate_detailed_report(results, working_directory, output_file='IO_Detailed_Analysis_Report.md'):
"""Genera un reporte detallado con tabla markdown"""
full_output_file = os.path.join(working_directory, 'resultados', output_file)
print(f"\n📄 Generando reporte detallado: {full_output_file}")
with open(full_output_file, 'w', encoding='utf-8') as f:
f.write("# Reporte Detallado de Análisis de Adaptación IO\n\n")
f.write(f"**Fecha de generación:** {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
# Estadísticas
total = len(results)
matched = len([r for r in results if r['twincat_variable']])
high_conf = len([r for r in results if r['confidence'] == 'High'])
medium_conf = len([r for r in results if r['confidence'] == 'Medium'])
f.write("## 📊 Estadísticas Generales\n\n")
f.write(f"- **Total adaptaciones procesadas:** {total}\n")
f.write(f"- **Variables encontradas:** {matched} ({matched/total*100:.1f}%)\n")
f.write(f"- **Coincidencias de alta confianza:** {high_conf}\n")
f.write(f"- **Coincidencias de media confianza:** {medium_conf}\n\n")
# Tabla de variables correlacionadas exitosamente
f.write("## ✅ Variables Correlacionadas Exitosamente\n\n")
matched_results = [r for r in results if r['twincat_variable']]
if matched_results:
# Encabezado de la tabla
f.write("| TIA Address | TIA Tag | TwinCAT Variable | TwinCAT Address | Tipo | Método | Confianza | Archivo Def. | Uso | Archivos Uso |\n")
f.write("|-------------|---------|------------------|-----------------|------|--------|-----------|--------------|-----|---------------|\n")
# Filas de datos
for result in matched_results:
usage_files_str = ', '.join(result['usage_files'][:3]) # Limitar a 3 archivos
if len(result['usage_files']) > 3:
usage_files_str += "..."
f.write(f"| {result['tia_address']} | "
f"`{result['master_tag']}` | "
f"`{result['twincat_variable']}` | "
f"`%{result['twincat_address']}` | "
f"`{result['twincat_type']}` | "
f"{result['match_type']} | "
f"{result['confidence']} | "
f"{result['definition_file']} | "
f"{result['usage_count']} | "
f"{usage_files_str} |\n")
f.write("\n")
# Tabla de variables no encontradas
f.write("## ❌ Variables No Encontradas\n\n")
unmatched_results = [r for r in results if not r['twincat_variable']]
if unmatched_results:
f.write("| TIA Address | TIA Tag |\n")
f.write("|-------------|----------|\n")
for result in unmatched_results:
f.write(f"| {result['tia_address']} | `{result['master_tag']}` |\n")
f.write(f"\n**Total no encontradas:** {len(unmatched_results)}\n\n")
# Recomendaciones
f.write("## 💡 Recomendaciones\n\n")
f.write("1. **Variables de alta confianza** pueden migrarse directamente\n")
f.write("2. **Variables de media confianza** requieren verificación manual\n")
f.write("3. **Variables no encontradas** requieren mapeo manual o pueden ser obsoletas\n")
f.write("4. Variables con uso extensivo son prioritarias para la migración\n\n")
# Resumen por confianza
f.write("## 📈 Distribución por Confianza\n\n")
f.write("| Nivel de Confianza | Cantidad | Porcentaje |\n")
f.write("|--------------------|----------|------------|\n")
f.write(f"| Alta | {high_conf} | {high_conf/total*100:.1f}% |\n")
f.write(f"| Media | {medium_conf} | {medium_conf/total*100:.1f}% |\n")
f.write(f"| No encontradas | {total-matched} | {(total-matched)/total*100:.1f}% |\n")
print(f"✅ Reporte detallado generado: {full_output_file}")
def main():
print("🚀 Iniciando análisis detallado de adaptación de IOs TwinCAT ↔ TIA Portal")
print("=" * 80)
# Cargar configuración
configs = load_configuration()
# Verificar que se cargó correctamente
if not configs:
print("Advertencia: No se pudo cargar la configuración, usando valores por defecto")
working_directory = "./"
else:
working_directory = configs.get("working_directory", "./")
# Verificar directorio de trabajo
if not os.path.exists(working_directory):
print(f"Error: El directorio de trabajo no existe: {working_directory}")
return
print(f"📁 Directorio de trabajo: {working_directory}")
# Crear directorio de resultados
results_dir = create_results_directory(working_directory)
# Cargar datos
tia_adaptations = load_tiaportal_adaptations(working_directory)
twincat_definitions = scan_twincat_definitions(working_directory)
twincat_usage = scan_twincat_usage(working_directory)
# Analizar correlaciones
results = analyze_adaptations(tia_adaptations, twincat_definitions, twincat_usage)
# Generar reportes en el directorio de resultados
generate_detailed_report(results, working_directory)
generate_json_output(results, working_directory)
# Generar CSV para análisis adicional
df = pd.DataFrame(results)
csv_file = results_dir / 'io_detailed_analysis.csv'
df.to_csv(csv_file, index=False, encoding='utf-8')
print(f"✅ Datos exportados a CSV: {csv_file}")
print(f"\n🎉 Análisis completado exitosamente!")
print(f"📁 Archivos generados en: {results_dir.absolute()}")
print(f" 📄 {results_dir / 'IO_Detailed_Analysis_Report.md'}")
print(f" 📄 {results_dir / 'io_adaptation_data.json'}")
print(f" 📄 {results_dir / 'io_detailed_analysis.csv'}")
return results
if __name__ == "__main__":
results = main()

View File

@ -1,115 +0,0 @@
# Análisis de Adaptación IO - TwinCAT ↔ TIA Portal
Scripts de análisis automático para correlacionar variables IO entre plataformas TwinCAT y TIA Portal en el proyecto SIDEL E5.007560.
## 📋 Descripción General
Este proyecto automatiza el análisis de adaptación de variables de entrada/salida (IO) entre:
- **TIA Portal** (Siemens) - Sistema actual
- **TwinCAT** (Beckhoff) - Sistema objetivo de migración
## 🔧 Scripts Incluidos
### 1. `x1_io_adaptation_script.py` - Análisis de Correlación IO
**Propósito:** Encuentra y correlaciona variables IO entre ambas plataformas generando reportes detallados.
**Archivos requeridos:**
- `IO Adapted.md` - Tabla de adaptaciones TIA Portal (debe estar en directorio raíz)
- `TwinCat/` - Directorio con archivos `.scl` de TwinCAT
- `TiaPortal/` - Directorio con archivos `.md` de TIA Portal
**Archivos generados:**
- `resultados/IO_Detailed_Analysis_Report.md` - Reporte con tablas markdown
- `resultados/io_adaptation_data.json` - Datos estructurados para análisis
- `resultados/io_detailed_analysis.csv` - Datos tabulares
### 2. `x2_code_snippets_generator.py` - Generador de Snippets de Código
**Propósito:** Genera snippets de código mostrando el uso real de cada variable en ambas plataformas.
**Archivos requeridos:**
- `resultados/io_adaptation_data.json` - Generado por el script 1
- `TwinCat/` - Directorio con archivos `.scl`
- `TiaPortal/` - Directorio con archivos `.md`
**Archivos generados:**
- `resultados/IO_Code_Snippets_Report.md` - Snippets de código con contexto
- `resultados/IO_Usage_Statistics.md` - Estadísticas de uso
## 🚀 Uso
### Paso 1: Ejecutar análisis de correlación
```bash
python x1_io_adaptation_script.py
```
### Paso 2: Generar snippets de código
```bash
python x2_code_snippets_generator.py
```
## 📁 Estructura de Directorios Requerida
```
proyecto/
├── x1_io_adaptation_script.py
├── x2_code_snippets_generator.py
├── IO Adapted.md # Tabla de adaptaciones TIA
├── TwinCat/ # Archivos .scl TwinCAT
│ ├── GLOBAL_VARIABLES_IN_OUT.scl
│ ├── INPUT.scl
│ └── ... (otros archivos .scl)
├── TiaPortal/ # Archivos .md TIA Portal
│ ├── Input.md
│ ├── Output.md
│ └── ... (otros archivos .md)
└── resultados/ # Directorio creado automáticamente
├── IO_Detailed_Analysis_Report.md
├── io_adaptation_data.json
├── io_detailed_analysis.csv
├── IO_Code_Snippets_Report.md
└── IO_Usage_Statistics.md
```
## 🔍 Funcionalidades Principales
### Script 1 - Análisis de Correlación
- ✅ Convierte direcciones TIA Portal a formato TwinCAT
- ✅ Busca variables por dirección exacta y similitud de nombres
- ✅ Calcula nivel de confianza de correlaciones
- ✅ Genera reportes en múltiples formatos (MD, JSON, CSV)
### Script 2 - Snippets de Código
- ✅ Muestra hasta 3 usos por variable por plataforma
- ✅ Contexto de 3 líneas (anterior, actual, siguiente)
- ✅ Links markdown a archivos fuente
- ✅ Estadísticas de uso y archivos más referenciados
## 📊 Resultados Típicos
- **Variables procesadas:** ~90-100 adaptaciones IO
- **Tasa de correlación:** ~70-80% de variables encontradas
- **Confianza alta:** Correlaciones por dirección exacta
- **Variable más usada:** Típicamente botones de reset/start/stop
## 🛠 Dependencias
```python
pandas
pathlib (incluida en Python 3.4+)
json (incluida en Python estándar)
re (incluida en Python estándar)
```
## 📝 Notas Importantes
1. **Orden de ejecución:** Ejecutar siempre el Script 1 antes que el Script 2
2. **Archivos fuente:** Verificar que existan los directorios TwinCat/ y TiaPortal/
3. **Codificación:** Los scripts manejan archivos con encoding UTF-8
4. **Rendimiento:** El Script 2 puede tardar algunos minutos procesando archivos grandes
## 👥 Proyecto
**Proyecto SIDEL:** E5.007560 - Modifica O&U - SAE235
**Automatización:** Migración TIA Portal → TwinCAT

View File

@ -1,315 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script para generar snippets de código de uso de variables IO
entre TwinCAT y TIA Portal - Proyecto SIDEL
Autor: Generado automáticamente
Proyecto: E5.007560 - Modifica O&U - SAE235
"""
import json
import os
import sys
import re
from pathlib import Path
from typing import Dict, List, Tuple, Optional
import pandas as pd
# Configurar el path al directorio raíz del proyecto
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
# Importar la función de configuración
from backend.script_utils import load_configuration
def load_adaptation_data(working_directory, json_file='io_adaptation_data.json'):
"""Carga los datos de adaptación desde el archivo JSON"""
full_json_file = os.path.join(working_directory, 'resultados', json_file)
print(f"📖 Cargando datos de adaptación desde: {full_json_file}")
if not os.path.exists(full_json_file):
print(f"⚠️ Archivo {full_json_file} no encontrado")
return None
with open(full_json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
print(f"✅ Cargados datos de {data['metadata']['total_adaptations']} adaptaciones")
return data
def find_variable_usage_in_file(file_path, variable_name, max_occurrences=3):
"""Encuentra el uso de una variable en un archivo específico y retorna el contexto"""
if not os.path.exists(file_path):
return []
usages = []
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
# Buscar todas las líneas que contienen la variable
found_lines = []
for line_num, line in enumerate(lines):
# Buscar la variable como palabra completa (no como parte de otra palabra)
if re.search(rf'\b{re.escape(variable_name)}\b', line):
found_lines.append((line_num, line.strip()))
if len(found_lines) >= max_occurrences:
break
# Para cada ocurrencia, obtener contexto (línea anterior, actual, siguiente)
for line_num, line_content in found_lines:
context = {
'line_number': line_num + 1, # Convertir a 1-indexado
'before': lines[line_num - 1].strip() if line_num > 0 else "",
'current': line_content,
'after': lines[line_num + 1].strip() if line_num < len(lines) - 1 else ""
}
usages.append(context)
except Exception as e:
print(f"⚠️ Error leyendo archivo {file_path}: {e}")
return usages
def find_tia_portal_usage(adaptation, working_directory):
"""Busca el uso de variables TIA Portal en archivos markdown"""
tia_address = adaptation['tia_portal']['address']
tia_tag = adaptation['tia_portal']['tag']
# Buscar en archivos TIA Portal (principalmente en archivos .md)
tia_usages = []
# Buscar en TiaPortal/ directory
tia_portal_dir = Path(working_directory) / 'TiaPortal'
if tia_portal_dir.exists():
for md_file in tia_portal_dir.glob('*.md'):
# Buscar por dirección TIA
address_usages = find_variable_usage_in_file(md_file, tia_address, 2)
for usage in address_usages:
usage['file'] = f"TiaPortal/{md_file.name}"
usage['search_term'] = tia_address
tia_usages.append(usage)
# Buscar por tag TIA si es diferente
if tia_tag != tia_address:
tag_usages = find_variable_usage_in_file(md_file, tia_tag, 1)
for usage in tag_usages:
usage['file'] = f"TiaPortal/{md_file.name}"
usage['search_term'] = tia_tag
tia_usages.append(usage)
# Limitar total de usos TIA
if len(tia_usages) >= 3:
break
return tia_usages[:3] # Máximo 3 usos TIA
def find_twincat_usage(adaptation, working_directory):
"""Busca el uso de variables TwinCAT en archivos .scl"""
if not adaptation['correlation']['found']:
return []
variable_name = adaptation['twincat']['variable']
usage_files = adaptation['usage']['usage_files']
twincat_usages = []
# Buscar en archivos TwinCAT
twincat_dir = Path(working_directory) / 'TwinCat'
if twincat_dir.exists():
for file_name in usage_files:
file_path = twincat_dir / file_name
if file_path.exists():
usages = find_variable_usage_in_file(file_path, variable_name, 2)
for usage in usages:
usage['file'] = f"TwinCat/{file_name}"
usage['search_term'] = variable_name
twincat_usages.append(usage)
# Limitar por archivo
if len(twincat_usages) >= 3:
break
return twincat_usages[:3] # Máximo 3 usos TwinCAT
def generate_code_snippets_report(data, working_directory, output_file='IO_Code_Snippets_Report.md'):
"""Genera el reporte con snippets de código"""
full_output_file = os.path.join(working_directory, 'resultados', output_file)
print(f"\n📄 Generando reporte de snippets: {full_output_file}")
matched_adaptations = [a for a in data['adaptations'] if a['correlation']['found']]
with open(full_output_file, 'w', encoding='utf-8') as f:
f.write("# Reporte de Snippets de Código - Adaptación IO\n\n")
f.write(f"**Fecha de generación:** {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"**Proyecto:** {data['metadata']['project']}\n\n")
f.write("## 📋 Resumen\n\n")
f.write(f"- **Variables analizadas:** {len(matched_adaptations)}\n")
f.write(f"- **Snippets generados:** Se muestran hasta 3 usos por plataforma\n")
f.write(f"- **Formato:** Contexto de 3 líneas (anterior, actual, siguiente)\n\n")
f.write("---\n\n")
# Procesar cada adaptación
for i, adaptation in enumerate(matched_adaptations, 1):
tia_address = adaptation['tia_portal']['address']
tia_tag = adaptation['tia_portal']['tag']
twincat_var = adaptation['twincat']['variable']
twincat_addr = adaptation['twincat']['address']
print(f" 📝 Procesando {i}/{len(matched_adaptations)}: {tia_address}{twincat_var}")
f.write(f"## {i}. {tia_address}{twincat_var}\n\n")
f.write(f"**TIA Portal:** `{tia_tag}` (`{tia_address}`)\n")
f.write(f"**TwinCAT:** `{twincat_var}` (`%{twincat_addr}`)\n")
f.write(f"**Tipo:** `{adaptation['twincat']['data_type']}`\n\n")
# Buscar usos en TIA Portal
f.write("### 🔵 Uso en TIA Portal\n\n")
tia_usages = find_tia_portal_usage(adaptation, working_directory)
if tia_usages:
for j, usage in enumerate(tia_usages):
f.write(f"**Uso {j+1}:** [{usage['file']}]({usage['file']}) - Línea {usage['line_number']}\n\n")
f.write("```scl\n")
if usage['before']:
f.write(f"{usage['before']}\n")
f.write(f">>> {usage['current']} // ← {usage['search_term']}\n")
if usage['after']:
f.write(f"{usage['after']}\n")
f.write("```\n\n")
else:
f.write("*No se encontraron usos específicos en archivos TIA Portal.*\n\n")
# Buscar usos en TwinCAT
f.write("### 🟢 Uso en TwinCAT\n\n")
twincat_usages = find_twincat_usage(adaptation, working_directory)
if twincat_usages:
for j, usage in enumerate(twincat_usages):
f.write(f"**Uso {j+1}:** [{usage['file']}]({usage['file']}) - Línea {usage['line_number']}\n\n")
f.write("```scl\n")
if usage['before']:
f.write(f"{usage['before']}\n")
f.write(f">>> {usage['current']} // ← {usage['search_term']}\n")
if usage['after']:
f.write(f"{usage['after']}\n")
f.write("```\n\n")
else:
f.write("*Variable definida pero no se encontraron usos específicos.*\n\n")
f.write("---\n\n")
print(f"✅ Reporte de snippets generado: {full_output_file}")
def generate_summary_statistics(data, working_directory, output_file='IO_Usage_Statistics.md'):
"""Genera estadísticas de uso de las variables"""
full_output_file = os.path.join(working_directory, 'resultados', output_file)
print(f"\n📊 Generando estadísticas de uso: {full_output_file}")
matched_adaptations = [a for a in data['adaptations'] if a['correlation']['found']]
# Calcular estadísticas
total_usage = sum(a['usage']['usage_count'] for a in matched_adaptations)
variables_with_usage = len([a for a in matched_adaptations if a['usage']['usage_count'] > 0])
# Variables más usadas
most_used = sorted(matched_adaptations, key=lambda x: x['usage']['usage_count'], reverse=True)[:10]
# Archivos más referenciados
file_usage = {}
for adaptation in matched_adaptations:
for file_name in adaptation['usage']['usage_files']:
file_usage[file_name] = file_usage.get(file_name, 0) + 1
top_files = sorted(file_usage.items(), key=lambda x: x[1], reverse=True)[:10]
with open(full_output_file, 'w', encoding='utf-8') as f:
f.write("# Estadísticas de Uso de Variables IO\n\n")
f.write(f"**Fecha de generación:** {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write("## 📊 Resumen General\n\n")
f.write(f"- **Variables correlacionadas:** {len(matched_adaptations)}\n")
f.write(f"- **Variables con uso documentado:** {variables_with_usage}\n")
f.write(f"- **Total de usos encontrados:** {total_usage}\n")
f.write(f"- **Promedio de usos por variable:** {total_usage/len(matched_adaptations):.1f}\n\n")
f.write("## 🔥 Top 10 Variables Más Usadas\n\n")
f.write("| Ranking | TIA Address | TwinCAT Variable | Usos | Archivos |\n")
f.write("|---------|-------------|------------------|------|----------|\n")
for i, adaptation in enumerate(most_used, 1):
files_str = ', '.join(adaptation['usage']['usage_files'][:3])
if len(adaptation['usage']['usage_files']) > 3:
files_str += '...'
f.write(f"| {i} | {adaptation['tia_portal']['address']} | "
f"`{adaptation['twincat']['variable']}` | "
f"{adaptation['usage']['usage_count']} | {files_str} |\n")
f.write("\n## 📁 Top 10 Archivos Más Referenciados\n\n")
f.write("| Ranking | Archivo | Variables Usadas |\n")
f.write("|---------|---------|------------------|\n")
for i, (file_name, count) in enumerate(top_files, 1):
f.write(f"| {i} | `{file_name}` | {count} |\n")
print(f"✅ Estadísticas de uso generadas: {full_output_file}")
def main():
print("🚀 Iniciando generación de snippets de código para adaptación IO")
print("=" * 70)
# Cargar configuración
configs = load_configuration()
# Verificar que se cargó correctamente
if not configs:
print("Advertencia: No se pudo cargar la configuración, usando valores por defecto")
working_directory = "./"
else:
working_directory = configs.get("working_directory", "./")
# Verificar directorio de trabajo
if not os.path.exists(working_directory):
print(f"Error: El directorio de trabajo no existe: {working_directory}")
return
print(f"📁 Directorio de trabajo: {working_directory}")
# Crear directorio de resultados si no existe
results_dir = Path(working_directory) / 'resultados'
results_dir.mkdir(exist_ok=True)
# Cargar datos de adaptación
data = load_adaptation_data(working_directory)
if not data:
print("❌ No se pudieron cargar los datos de adaptación")
return
# Generar reporte de snippets
generate_code_snippets_report(data, working_directory)
# Generar estadísticas de uso
generate_summary_statistics(data, working_directory)
print(f"\n🎉 Generación completada exitosamente!")
print(f"📁 Archivos generados en: {results_dir.absolute()}")
print(f" 📄 {results_dir / 'IO_Code_Snippets_Report.md'}")
print(f" 📄 {results_dir / 'IO_Usage_Statistics.md'}")
if __name__ == "__main__":
main()

View File

@ -1,11 +1,25 @@
{
"folders": [
{
"name": "XML Parser to SCL",
"path": "."
},
{
"path": "C:/Trabajo/SIDEL/13 - E5.007560 - Modifica O&U - SAE235/Reporte/ExportTia"
}
],
"settings": {}
"settings": {
"python.defaultInterpreterPath": "python",
"files.associations": {
"*.xml": "xml",
"*.scl": "structured-text"
}
},
"extensions": {
"recommendations": [
"ms-python.python",
"ms-python.flake8",
"ms-python.black-formatter"
]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,5 +15,5 @@
"xref_source_subdir": "source"
},
"level3": {},
"working_directory": "C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia"
"working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source"
}

View File

@ -1,8 +1,8 @@
{
"path": "C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia",
"path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
"history": [
"C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia",
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
"C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia",
"D:\\Trabajo\\VM\\22 - 93841 - Sidel - Tilting\\Reporte\\TiaExports",
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML",
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"

26667
data/log.txt

File diff suppressed because it is too large Load Diff

View File

@ -978,3 +978,45 @@ class CSharpLauncherManager:
json.dump(data, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error cleaning favorites for project {project_id}: {e}")
def find_solution_file(self, project_id: str) -> Optional[str]:
"""
Buscar archivo .sln en el directorio del proyecto.
Prioridades:
1. Si hay un solo .sln, retornarlo
2. Si hay múltiples .sln, buscar uno que coincida con el nombre del directorio
3. Si no hay coincidencias exactas, retornar el primero alfabéticamente
4. Si no hay .sln, retornar None
"""
try:
project = self.get_csharp_project(project_id)
if not project:
return None
project_dir = project["directory"]
if not os.path.isdir(project_dir):
return None
# Buscar archivos .sln en el directorio raíz del proyecto
sln_pattern = os.path.join(project_dir, "*.sln")
sln_files = glob.glob(sln_pattern)
if not sln_files:
return None
if len(sln_files) == 1:
return sln_files[0]
# Si hay múltiples archivos, buscar uno que coincida con el nombre del directorio
project_name = os.path.basename(project_dir.rstrip(os.sep))
for sln_file in sln_files:
sln_name = os.path.splitext(os.path.basename(sln_file))[0]
if sln_name.lower() == project_name.lower():
return sln_file
# Si no hay coincidencias exactas, retornar el primero alfabéticamente
return sorted(sln_files)[0]
except Exception as e:
print(f"Error finding solution file for project {project_id}: {e}")
return None

View File

@ -3,13 +3,17 @@
Factory class for creating language detection services
"""
from typing import Optional, Set
from .base import LanguageDetectionService
from .langid_service import LangIdService
class LanguageFactory:
"""Factory class for creating language detection service instances"""
@staticmethod
def create_service(service_type: str, allowed_languages: Optional[Set[str]] = None, **kwargs) -> Optional['LanguageDetectionService']:
def create_service(
service_type: str, allowed_languages: Optional[Set[str]] = None, **kwargs
) -> Optional[LanguageDetectionService]:
"""
Create an instance of the specified language detection service

View File

@ -0,0 +1,103 @@
# services/llm/claude_service.py
"""
Claude (Anthropic) service implementation
"""
import anthropic
from typing import Dict, List
import json
from .base import LLMService
from config.api_keys import APIKeyManager
from utils.logger import setup_logger
class ClaudeService(LLMService):
def __init__(
self,
model: str = "claude-3-7-sonnet-20250219", # Debemos usar el modelo claude-3-7-sonnet-20250219
temperature: float = 0.3,
max_tokens: int = 16000,
):
api_key = APIKeyManager.get_claude_key()
if not api_key:
raise ValueError(
"Claude API key not found. Please set the CLAUDE_API_KEY environment variable."
)
self.client = anthropic.Anthropic(api_key=api_key)
self.model = model
self.temperature = temperature
self.max_tokens = max_tokens
self.logger = setup_logger("claude")
def generate_text(self, prompt: str) -> str:
self.logger.info(f"--- PROMPT ---\n{prompt}")
try:
message = self.client.messages.create(
model=self.model,
max_tokens=self.max_tokens,
temperature=self.temperature,
messages=[
{
"role": "user",
"content": prompt,
}
],
)
response_content = message.content[0].text
self.logger.info(f"--- RESPONSE ---\n{response_content}")
return response_content
except Exception as e:
self.logger.error(f"Error in Claude API call: {e}")
print(f"Error in Claude API call: {e}")
return None
def get_similarity_scores(self, texts_pairs: Dict[str, List[str]]) -> List[float]:
# Claude's API doesn't have a dedicated similarity or JSON mode endpoint as straightforward as others.
# We will instruct it to return JSON.
system_prompt = (
"You are an expert in semantic analysis. Evaluate the semantic similarity between the pairs of texts provided. "
"Return your response ONLY as a JSON object containing a single key 'similarity_scores' with a list of floats from 0.0 to 1.0. "
"Do not include any other text, explanation, or markdown formatting. The output must be a valid JSON."
)
request_payload = json.dumps(texts_pairs)
try:
message = self.client.messages.create(
model=self.model,
max_tokens=self.max_tokens,
temperature=self.temperature,
system=system_prompt,
messages=[
{
"role": "user",
"content": request_payload,
}
],
)
response_content = message.content[0].text
try:
# Find the JSON part of the response
json_start = response_content.find("{")
json_end = response_content.rfind("}") + 1
if json_start == -1 or json_end == 0:
raise ValueError("No JSON object found in the response.")
json_str = response_content[json_start:json_end]
scores_data = json.loads(json_str)
if isinstance(scores_data, dict) and "similarity_scores" in scores_data:
return scores_data["similarity_scores"]
else:
raise ValueError("Unexpected JSON format from Claude.")
except (json.JSONDecodeError, ValueError) as e:
print(f"Error decoding Claude JSON response: {e}")
raise ValueError(
"Could not decode or parse similarity scores from Claude response."
)
except Exception as e:
print(f"Error in Claude similarity calculation: {e}")
return None

View File

@ -0,0 +1,83 @@
# services/llm/gemini_service.py
"""
Gemini (Google) service implementation
"""
import google.generativeai as genai
from typing import Dict, List
import json
from .base import LLMService
from config.api_keys import APIKeyManager
from utils.logger import setup_logger
class GeminiService(LLMService):
def __init__(
self,
model: str = "gemini-1.5-flash",
temperature: float = 0.3,
max_tokens: int = 16000,
):
api_key = APIKeyManager.get_gemini_key()
if not api_key:
raise ValueError(
"Gemini API key not found. Please set the GEMINI_API_KEY environment variable."
)
genai.configure(api_key=api_key)
self.model = genai.GenerativeModel(model)
self.temperature = temperature
self.max_tokens = max_tokens
self.logger = setup_logger("gemini")
def generate_text(self, prompt: str) -> str:
self.logger.info(f"--- PROMPT ---\n{prompt}")
try:
generation_config = genai.types.GenerationConfig(
max_output_tokens=self.max_tokens, temperature=self.temperature
)
response = self.model.generate_content(
prompt, generation_config=generation_config
)
response_content = response.text
self.logger.info(f"--- RESPONSE ---\n{response_content}")
return response_content
except Exception as e:
self.logger.error(f"Error in Gemini API call: {e}")
print(f"Error in Gemini API call: {e}")
return None
def get_similarity_scores(self, texts_pairs: Dict[str, List[str]]) -> List[float]:
system_prompt = (
"You are an expert in semantic analysis. Evaluate the semantic similarity between the pairs of texts provided. "
"Return your response ONLY as a JSON object containing a single key 'similarity_scores' with a list of floats from 0.0 to 1.0. "
"Do not include any other text, explanation, or markdown formatting. The output must be a valid JSON."
)
request_payload = json.dumps(texts_pairs)
full_prompt = f"{system_prompt}\n\n{request_payload}"
try:
generation_config = genai.types.GenerationConfig(
max_output_tokens=self.max_tokens,
temperature=self.temperature,
response_mime_type="application/json",
)
response = self.model.generate_content(
full_prompt, generation_config=generation_config
)
response_content = response.text
try:
scores_data = json.loads(response_content)
if isinstance(scores_data, dict) and "similarity_scores" in scores_data:
return scores_data["similarity_scores"]
else:
raise ValueError("Unexpected JSON format from Gemini.")
except (json.JSONDecodeError, ValueError) as e:
print(f"Error decoding Gemini JSON response: {e}")
raise ValueError(
"Could not decode or parse similarity scores from Gemini response."
)
except Exception as e:
print(f"Error in Gemini similarity calculation: {e}")
return None

View File

@ -1,63 +1,106 @@
# services/llm/grok_service.py
"""
Grok service implementation
Grok (xAI) service implementation
"""
import httpx
from typing import Dict, List, Optional
import json
from .base import LLMService
from config.api_keys import APIKeyManager
from utils.logger import setup_logger
class GrokService(LLMService):
def __init__(self, model: str = "grok-1", temperature: float = 0.3):
def __init__(
self,
model: str = "grok-3-mini-fast",
temperature: float = 0.3,
max_tokens: int = 16000,
): # Debemos usar el modelo grok-3-mini-fast
api_key = APIKeyManager.get_grok_key()
if not api_key:
raise ValueError("Grok API key not found. Please set up your API keys.")
raise ValueError(
"Grok API key not found. Please set the GROK_API_KEY environment variable."
)
self.api_key = api_key
self.model = model
self.temperature = temperature
self.max_tokens = max_tokens
self.base_url = "https://api.x.ai/v1"
self.client = httpx.Client(
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
}
)
self.logger = setup_logger("grok_xai")
def _send_request(self, payload: Dict) -> Optional[Dict]:
"""Sends a request to the Grok API."""
try:
response = self.client.post(
f"{self.base_url}/chat/completions", json=payload, timeout=60
)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
self.logger.error(
f"Error in Grok API call: {e.response.status_code} - {e.response.text}"
)
print(
f"Error in Grok API call: {e.response.status_code} - {e.response.text}"
)
return None
except Exception as e:
self.logger.error(f"An unexpected error occurred: {e}")
print(f"An unexpected error occurred: {e}")
return None
def generate_text(self, prompt: str) -> str:
"""
Generate text using the Grok API
TODO: Update this method when Grok API is available
"""
try:
# Placeholder for Grok API implementation
# Update this when the API is released
raise NotImplementedError("Grok API is not implemented yet")
except Exception as e:
print(f"Error in Grok API call: {e}")
return None
self.logger.info(f"--- PROMPT ---\n{prompt}")
payload = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
"temperature": self.temperature,
"max_tokens": self.max_tokens,
}
response_data = self._send_request(payload)
if response_data and response_data.get("choices"):
response_content = response_data["choices"][0]["message"]["content"]
self.logger.info(f"--- RESPONSE ---\n{response_content}")
return response_content
return "Failed to get a response from Grok."
def get_similarity_scores(self, texts_pairs: Dict[str, List[str]]) -> List[float]:
"""
Calculate similarity scores using the Grok API
TODO: Update this method when Grok API is available
"""
try:
system_prompt = (
"Evaluate the semantic similarity between the following table of pairs of texts "
"in json format on a scale from 0 to 1. Return the similarity scores for every "
"row in JSON format as a list of numbers, without any additional text or formatting."
)
system_prompt = (
"You are an expert in semantic analysis. Evaluate the semantic similarity between the pairs of texts provided. "
"Return your response ONLY as a JSON object containing a single key 'similarity_scores' with a list of floats from 0.0 to 1.0. "
"Do not include any other text, explanation, or markdown formatting. The output must be a valid JSON."
)
request_payload = json.dumps(texts_pairs)
request_payload = json.dumps(texts_pairs)
payload = {
"model": self.model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": request_payload},
],
"temperature": self.temperature,
"max_tokens": self.max_tokens,
"response_format": {"type": "json_object"},
}
# Placeholder for Grok API implementation
# Update this when the API is released
raise NotImplementedError("Grok API is not implemented yet")
except Exception as e:
print(f"Error in Grok similarity calculation: {e}")
return None
# Update config/api_keys.py to include Grok
@classmethod
def get_grok_key(cls) -> Optional[str]:
"""Get Grok API key from environment or stored configuration"""
return (
os.getenv('GROK_API_KEY') or
cls._get_stored_key('grok')
)
response_data = self._send_request(payload)
if response_data and response_data.get("choices"):
response_content = response_data["choices"][0]["message"]["content"]
try:
scores_data = json.loads(response_content)
if isinstance(scores_data, dict) and "similarity_scores" in scores_data:
return scores_data["similarity_scores"]
else:
raise ValueError("Unexpected JSON format from Grok.")
except (json.JSONDecodeError, ValueError) as e:
print(f"Error decoding Grok JSON response: {e}")
return None
return None

View File

@ -0,0 +1,84 @@
# services/llm/groq_service.py
"""
Groq service implementation
"""
from groq import Groq
from typing import Dict, List
import json
from .base import LLMService
from config.api_keys import APIKeyManager
from utils.logger import setup_logger
class GroqService(LLMService):
def __init__(
self,
model: str = "llama3-8b-8192",
temperature: float = 0.3,
max_tokens: int = 8000,
):
api_key = APIKeyManager.get_groq_key()
if not api_key:
raise ValueError(
"Groq API key not found. Please set the GROQ_API_KEY environment variable."
)
self.client = Groq(api_key=api_key)
self.model = model
self.temperature = temperature
self.max_tokens = max_tokens
self.logger = setup_logger("groq")
def generate_text(self, prompt: str) -> str:
self.logger.info(f"--- PROMPT ---\n{prompt}")
try:
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=self.temperature,
max_tokens=self.max_tokens,
)
response_content = response.choices[0].message.content
self.logger.info(f"--- RESPONSE ---\n{response_content}")
return response_content
except Exception as e:
self.logger.error(f"Error in Groq API call: {e}")
print(f"Error in Groq API call: {e}")
return None
def get_similarity_scores(self, texts_pairs: Dict[str, List[str]]) -> List[float]:
system_prompt = (
"Evaluate the semantic similarity between the following table of pairs of texts in json format on a scale from 0 to 1. "
"Return the similarity scores for every row in JSON format as a list of numbers, without any additional text or formatting."
)
request_payload = json.dumps(texts_pairs)
try:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": request_payload},
],
temperature=self.temperature,
max_tokens=self.max_tokens,
response_format={"type": "json_object"},
)
response_content = response.choices[0].message.content
try:
scores = json.loads(response_content)
if isinstance(scores, dict) and "similarity_scores" in scores:
return scores["similarity_scores"]
elif isinstance(scores, list):
return scores
else:
raise ValueError("Unexpected response format")
except json.JSONDecodeError:
raise ValueError("Could not decode response as JSON")
except Exception as e:
print(f"Error in Groq similarity calculation: {e}")
return None

1157
services/llm/gtpask.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,24 +5,32 @@ Factory class for creating LLM services
from typing import Optional
from .openai_service import OpenAIService
from .ollama_service import OllamaService
from .groq_service import GroqService
from .claude_service import ClaudeService
from .grok_service import GrokService
from .gemini_service import GeminiService
from .base import LLMService
class LLMFactory:
"""Factory class for creating LLM service instances"""
@staticmethod
def create_service(service_type: str, **kwargs) -> Optional['LLMService']:
def create_service(service_type: str, **kwargs) -> Optional[LLMService]:
"""
Create an instance of the specified LLM service
Args:
service_type: Type of LLM service ("openai", "ollama", "grok")
service_type: Type of LLM service ("openai", "ollama", "groq", "claude", "grok")
**kwargs: Additional arguments for service initialization
"""
services = {
"openai": OpenAIService,
"ollama": OllamaService,
"grok": GrokService
"groq": GroqService,
"claude": ClaudeService,
"grok": GrokService,
"gemini": GeminiService,
}
service_class = services.get(service_type.lower())

View File

@ -6,19 +6,29 @@ import ollama
import json
from typing import Dict, List
from .base import LLMService
from utils.logger import setup_logger
class OllamaService(LLMService):
def __init__(self, model: str = "llama3.1"):
def __init__(self, model: str = "qwen3:latest", max_tokens: int = 4000):
self.model = model
# Explicitly set the host to avoid potential DNS/proxy issues with 'localhost'
self.client = ollama.Client(host="127.0.0.1:11434")
self.max_tokens = max_tokens
self.logger = setup_logger("ollama")
def generate_text(self, prompt: str) -> str:
self.logger.info(f"--- PROMPT ---\n{prompt}")
try:
response = ollama.generate(
model=self.model,
prompt=prompt
options = {"num_predict": self.max_tokens}
response = self.client.generate(
model=self.model, prompt=prompt, options=options
)
return response["response"]
response_content = response["response"]
self.logger.info(f"--- RESPONSE ---\n{response_content}")
return response_content
except Exception as e:
self.logger.error(f"Error in Ollama API call: {e}")
print(f"Error in Ollama API call: {e}")
return None
@ -32,9 +42,9 @@ class OllamaService(LLMService):
prompt = f"{system_prompt}\n\n{request_payload}"
try:
response = ollama.generate(
model=self.model,
prompt=prompt
options = {"num_predict": self.max_tokens}
response = self.client.generate(
model=self.model, prompt=prompt, options=options
)
try:

View File

@ -6,28 +6,43 @@ from openai import OpenAI
from typing import Dict, List
import json
from .base import LLMService
from openai_api_key import openai_api_key
from config.api_keys import APIKeyManager
from utils.logger import setup_logger
class OpenAIService(LLMService):
def __init__(self, model: str = "gpt-4o-mini", temperature: float = 0.3):
api_key = openai_api_key()
def __init__(
self,
model: str = "gpt-4o-mini",
temperature: float = 0.3,
max_tokens: int = 16000,
):
api_key = APIKeyManager.get_openai_key()
if not api_key:
raise ValueError("OpenAI API key not found. Please set up your API keys.")
raise ValueError(
"OpenAI API key not found. Please set the OPENAI_API_KEY environment variable."
)
self.client = OpenAI(api_key=api_key)
self.model = model
self.temperature = temperature
self.max_tokens = max_tokens
self.logger = setup_logger("openai")
def generate_text(self, prompt: str) -> str:
self.logger.info(f"--- PROMPT ---\n{prompt}")
try:
response = self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=self.temperature,
max_tokens=1500
max_tokens=self.max_tokens,
)
return response.choices[0].message.content
response_content = response.choices[0].message.content
self.logger.info(f"--- RESPONSE ---\n{response_content}")
return response_content
except Exception as e:
self.logger.error(f"Error in OpenAI API call: {e}")
print(f"Error in OpenAI API call: {e}")
return None
@ -44,10 +59,10 @@ class OpenAIService(LLMService):
model=self.model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": request_payload}
{"role": "user", "content": request_payload},
],
temperature=self.temperature,
max_tokens=1500
max_tokens=self.max_tokens,
)
response_content = response.choices[0].message.content

90
services/llm/test_llm.py Normal file
View File

@ -0,0 +1,90 @@
import os
from dotenv import load_dotenv
from llm_factory import LLMFactory
# Load environment variables from .env file
load_dotenv()
def main():
"""Main function to test the LLM services."""
print("Testing LLM Services...")
# --- Test OpenAI ---
try:
print("\n--- Testing OpenAI ---")
openai_service = LLMFactory.create_service("openai")
if openai_service:
prompt_openai = "Explain the importance of structured JSON output from LLMs in one sentence."
response_openai = openai_service.generate_text(prompt_openai)
print(f"OpenAI Prompt: {prompt_openai}")
print(f"OpenAI Response: {response_openai}")
else:
print("Failed to create OpenAI service.")
except Exception as e:
print(f"Error testing OpenAI: {e}")
# --- Test Groq ---
try:
print("\n--- Testing Groq ---")
groq_service = LLMFactory.create_service("groq")
if groq_service:
prompt_groq = (
"Explain the concept of 'inference speed' for LLMs in one sentence."
)
response_groq = groq_service.generate_text(prompt_groq)
print(f"Groq Prompt: {prompt_groq}")
print(f"Groq Response: {response_groq}")
else:
print("Failed to create Groq service.")
except Exception as e:
print(f"Error testing Groq: {e}")
# --- Test Claude ---
try:
print("\n--- Testing Claude ---")
claude_service = LLMFactory.create_service("claude")
if claude_service:
prompt_claude = (
"What is Anthropic's Constitutional AI concept in one sentence?"
)
response_claude = claude_service.generate_text(prompt_claude)
print(f"Claude Prompt: {prompt_claude}")
print(f"Claude Response: {response_claude}")
else:
print("Failed to create Claude service.")
except Exception as e:
print(f"Error testing Claude: {e}")
# --- Test Grok (xAI) ---
try:
print("\n--- Testing Grok (xAI) ---")
grok_service = LLMFactory.create_service("grok")
if grok_service:
prompt_grok = "What is the mission of xAI in one sentence?"
response_grok = grok_service.generate_text(prompt_grok)
print(f"Grok Prompt: {prompt_grok}")
print(f"Grok Response: {response_grok}")
else:
print("Failed to create Grok service.")
except Exception as e:
print(f"Error testing Grok (xAI): {e}")
# --- Test Ollama ---
try:
print("\n--- Testing Ollama ---")
# Make sure you have an Ollama model running, e.g., `ollama run llama3.1`
ollama_service = LLMFactory.create_service("ollama", model="llama3.1")
if ollama_service:
prompt_ollama = "What is Ollama?"
response_ollama = ollama_service.generate_text(prompt_ollama)
print(f"Ollama Prompt: {prompt_ollama}")
print(f"Ollama Response: {response_ollama}")
else:
print("Failed to create Ollama service.")
except Exception as e:
print(f"Error testing Ollama: {e}")
if __name__ == "__main__":
main()

View File

@ -844,6 +844,19 @@ async function openCSharpProjectInEditor(editor) {
const projectId = window.csharpLauncherManager.currentProject.id;
try {
// Para VS2022, primero verificar si hay archivo .sln disponible
let solutionInfo = null;
if (editor === 'vs2022') {
try {
const solutionResponse = await fetch(`/api/csharp-solution-file/${projectId}`);
if (solutionResponse.ok) {
solutionInfo = await solutionResponse.json();
}
} catch (err) {
console.warn('No se pudo verificar archivo de solución:', err);
}
}
const response = await fetch(`/api/open-editor/${editor}/csharp/${projectId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
@ -852,10 +865,22 @@ async function openCSharpProjectInEditor(editor) {
const result = await response.json();
if (response.ok && result.status === 'success') {
const editorName = editor === 'cursor' ? 'Cursor' : 'Visual Studio 2022';
let message = `${editorName} abierto exitosamente`;
// Agregar información específica para VS2022
if (editor === 'vs2022' && solutionInfo) {
if (solutionInfo.exists && solutionInfo.solution_file) {
const solutionName = solutionInfo.solution_file.split('\\').pop().split('/').pop();
message = `${editorName} abierto con solución: ${solutionName}`;
} else {
message = `${editorName} abierto (carpeta del proyecto)`;
}
}
if (window.csharpLauncherManager && window.csharpLauncherManager.showNotification) {
window.csharpLauncherManager.showNotification(`${editorName} abierto exitosamente`, 'success');
window.csharpLauncherManager.showNotification(message, 'success');
} else {
alert(`${editorName} abierto exitosamente`);
alert(message);
}
} else {
const errorMsg = `Error: ${result.message}`;