Implementación de nuevas funcionalidades en el Launcher GUI

- Se añadieron nuevas rutas API para gestionar procesos de lanzadores, incluyendo la activación de foco y la terminación de procesos.
- Se mejoró la ejecución de scripts GUI al permitir especificar el directorio de trabajo y el tipo de ejecutable (python o pythonw).
- Se actualizaron los logs de ejecución para reflejar nuevas ubicaciones y detalles de los scripts.
- Se ajustaron los archivos de configuración y se mejoró la interfaz de usuario para soportar las nuevas funcionalidades.
This commit is contained in:
Miguel 2025-06-06 16:35:11 +02:00
parent 71a2a63de4
commit 88806ee4e4
20 changed files with 2158 additions and 245 deletions

222
app.py
View File

@ -8,6 +8,7 @@ from datetime import datetime
import time # Added for shutdown delay import time # Added for shutdown delay
import sys # Added for platform detection import sys # Added for platform detection
import subprocess # Add this to the imports at the top import subprocess # Add this to the imports at the top
import shutil # For shutil.whichimport os
# --- Imports for System Tray Icon --- # --- Imports for System Tray Icon ---
import threading import threading
@ -609,9 +610,11 @@ def execute_gui_script():
group_id = data["group_id"] group_id = data["group_id"]
script_name = data["script_name"] script_name = data["script_name"]
script_args = data.get("args", []) script_args = data.get("args", [])
working_dir = data.get("working_dir", None)
use_pythonw = data.get("use_pythonw", False) # Por defecto python.exe para logging
result = launcher_manager.execute_gui_script( result = launcher_manager.execute_gui_script(
group_id, script_name, script_args, broadcast_message group_id, script_name, script_args, broadcast_message, working_dir, use_pythonw
) )
return jsonify(result) return jsonify(result)
except Exception as e: except Exception as e:
@ -690,8 +693,225 @@ def get_group_icon(launcher_type, group_id):
except Exception as e: except Exception as e:
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
# Nuevas APIs para gestión de procesos y Markdown
@app.route("/api/launcher-process-focus/<int:pid>", methods=["POST"])
def focus_launcher_process(pid):
"""Activar foco de un proceso"""
try:
result = launcher_manager.focus_process(pid)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/launcher-process-terminate/<int:pid>", methods=["POST"])
def terminate_launcher_process(pid):
"""Cerrar un proceso"""
try:
result = launcher_manager.terminate_process(pid)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/launcher-running-processes")
def get_launcher_running_processes():
"""Obtener procesos en ejecución"""
try:
processes = launcher_manager.get_running_processes()
return jsonify({"processes": processes})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/launcher-open-vscode/<group_id>", methods=["POST"])
def open_launcher_group_in_vscode(group_id):
"""Abrir grupo del launcher en VS Code"""
try:
group = launcher_manager.get_launcher_group(group_id)
if not group:
return jsonify({"status": "error", "message": "Grupo no encontrado"}), 404
script_group_path = group["directory"]
if not os.path.isdir(script_group_path):
return jsonify({
"status": "error",
"message": f"Directorio del grupo '{group['name']}' no encontrado"
}), 404
# VS Code executable path
vscode_path = r"C:\Users\migue\AppData\Local\Programs\Microsoft VS Code\Code.exe"
if not os.path.isfile(vscode_path):
return jsonify({
"status": "error",
"message": f"VS Code no encontrado en: {vscode_path}"
}), 404
print(f"Launching VS Code for launcher group: {group['name']}")
print(f"Opening directory: {script_group_path}")
process = subprocess.Popen(f'"{vscode_path}" "{script_group_path}"', shell=True)
print(f"Process started with PID: {process.pid}")
return jsonify({
"status": "success",
"message": f"VS Code abierto en: {script_group_path}"
})
except Exception as e:
print(f"Error opening VS Code for launcher group '{group_id}': {str(e)}")
return jsonify({
"status": "error",
"message": f"Error al abrir VS Code: {str(e)}"
}), 500
@app.route("/api/launcher-markdown/<group_id>")
def get_launcher_markdown_files(group_id):
"""Obtener archivos Markdown de un grupo"""
try:
markdown_files = launcher_manager.get_markdown_files(group_id)
return jsonify({"files": markdown_files})
except Exception as e:
print(f"Error getting markdown files for group {group_id}: {e}")
# Devolver lista vacía en lugar de error para no interferir con scripts
return jsonify({"files": []})
@app.route("/api/launcher-markdown-content/<group_id>/<path:relative_path>")
def get_launcher_markdown_content(group_id, relative_path):
"""Obtener contenido de un archivo Markdown"""
try:
result = launcher_manager.read_markdown_file(group_id, relative_path)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
# --- Global Error Handler (for debugging unhandled exceptions) ---
@app.errorhandler(Exception)
def handle_unhandled_exception(e):
# Log the error with traceback
app.logger.error('Unhandled Exception: %s', e, exc_info=True)
# Return a JSON response for API calls, or HTML for others
if request.path.startswith('/api/'):
return jsonify({"status": "error", "message": f"Internal Server Error: {str(e)}"}), 500
else:
return "<h1>Internal Server Error</h1><p>An unhandled error occurred.</p>", 500
# === FIN LAUNCHER GUI APIs === # === FIN LAUNCHER GUI APIs ===
# --- Helper function to find VS Code ---
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)
vscode_env_path = os.getenv("VSCODE_PATH")
if vscode_env_path and os.path.isfile(vscode_env_path):
return vscode_env_path
common_paths = []
local_app_data = os.getenv('LOCALAPPDATA')
if local_app_data:
common_paths.append(os.path.join(local_app_data, r"Programs\Microsoft VS Code\Code.exe"))
common_paths.extend([
r"C:\Program Files\Microsoft VS Code\Code.exe",
r"C:\Program Files (x86)\Microsoft VS Code\Code.exe",
])
for path in common_paths:
if os.path.isfile(path):
return path
return shutil.which("code") # Busca 'code' en el PATH
@app.route("/api/open-editor/<editor>/<group_system>/<group_id>", methods=["POST"])
def open_group_in_editor(editor, group_system, group_id):
"""Ruta unificada para abrir grupos en diferentes editores"""
try:
# Validar editor
if editor not in ['vscode', 'cursor']:
return jsonify({
"status": "error",
"message": f"Editor '{editor}' no soportado. Usar 'vscode' o 'cursor'"
}), 400
# Determinar directorio según el sistema
if group_system == 'config':
script_group_path = os.path.join(config_manager.script_groups_path, group_id)
if not os.path.isdir(script_group_path):
return jsonify({
"status": "error",
"message": f"Directorio del grupo config '{group_id}' no encontrado"
}), 404
elif group_system == 'launcher':
group = launcher_manager.get_launcher_group(group_id)
if not group:
return jsonify({
"status": "error",
"message": f"Grupo launcher '{group_id}' no encontrado"
}), 404
script_group_path = group["directory"]
if not os.path.isdir(script_group_path):
return jsonify({
"status": "error",
"message": f"Directorio del grupo launcher '{group['name']}' no encontrado"
}), 404
else:
return jsonify({
"status": "error",
"message": f"Sistema de grupo '{group_system}' no válido. Usar 'config' o 'launcher'"
}), 400
# Definir rutas de ejecutables
if editor == 'vscode':
editor_path = r"C:\Users\migue\AppData\Local\Programs\Microsoft VS Code\Code.exe"
editor_name = "VS Code"
elif editor == 'cursor':
# Rutas comunes donde se instala Cursor
possible_cursor_paths = [
r"C:\Users\migue\AppData\Local\Programs\cursor\Cursor.exe",
r"C:\Program Files\Cursor\Cursor.exe",
r"C:\Program Files (x86)\Cursor\Cursor.exe"
]
editor_path = None
for path in possible_cursor_paths:
if os.path.isfile(path):
editor_path = path
break
if not editor_path:
# Intentar buscar en PATH
editor_path = shutil.which("cursor")
if not editor_path:
return jsonify({
"status": "error",
"message": f"Cursor no encontrado. Intenté en: {', '.join(possible_cursor_paths)}"
}), 404
editor_name = "Cursor"
# Verificar que el ejecutable existe
if not os.path.isfile(editor_path):
return jsonify({
"status": "error",
"message": f"{editor_name} no encontrado en: {editor_path}"
}), 404
print(f"Launching {editor_name} from: {editor_path}")
print(f"Opening directory: {script_group_path}")
# Ejecutar el editor
process = subprocess.Popen(f'"{editor_path}" "{script_group_path}"', 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}"
})
except Exception as e:
print(f"Error opening {editor} for {group_system} group '{group_id}': {str(e)}")
return jsonify({
"status": "error",
"message": f"Error al abrir {editor}: {str(e)}"
}), 500
if __name__ == "__main__": if __name__ == "__main__":
# --- Start Flask in a background thread --- # --- Start Flask in a background thread ---

View File

@ -1,35 +1,36 @@
--- Log de Ejecución: x1_export_CAx.py --- --- Log de Ejecución: x1_export_CAx.py ---
Grupo: IO_adaptation Grupo: IO_adaptation
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Inicio: 2025-05-15 10:41:11 Inicio: 2025-06-06 16:30:18
Fin: 2025-05-15 10:43:18 Fin: 2025-06-06 16:33:55
Duración: 0:02:07.367695 Duración: 0:03:37.052535
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
--- TIA Portal Project CAx Exporter and Analyzer --- --- TIA Portal Project CAx Exporter and Analyzer ---
Selected Project: C:/Trabajo/SIDEL/06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)/InLavoro/PLC/_NEW/SAE196_c0.2/SAE196_c0.2.ap18 Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19
Using Output Directory (Working Directory): C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Will export CAx data to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml Detected TIA Portal version: 19.0 (from extension .ap19)
Will generate summary to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Summary.md Will export CAx data to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
Export log file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.log Will generate summary to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
Export log file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.log
Connecting to TIA Portal V18.0... Connecting to TIA Portal V19.0...
2025-05-15 10:41:33,504 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog. 2025-06-06 16:30:25,861 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
2025-05-15 10:41:33,524 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Global OpenPortal - With user interface 2025-06-06 16:30:25,895 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface
Connected. Connected.
Opening project: SAE196_c0.2.ap18... Opening project: 98050_PLC_01.ap19...
2025-05-15 10:42:05,513 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal OpenProject - Open project... C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\InLavoro\PLC\_NEW\SAE196_c0.2\SAE196_c0.2.ap18 2025-06-06 16:32:34,404 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject - Open project... D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_01\98050_PLC_01.ap19
Project opened. Project opened.
Exporting CAx data for the project to C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml... Exporting CAx data for the project to D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml...
CAx data exported successfully. CAx data exported successfully.
Closing TIA Portal... Closing TIA Portal...
2025-05-15 10:43:15,299 [1] INFO Siemens.TiaPortal.OpennessApi18.Implementations.Portal ClosePortal - Close TIA Portal 2025-06-06 16:33:52,008 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal ClosePortal - Close TIA Portal
TIA Portal closed. TIA Portal closed.
Parsing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml Parsing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Summary.md Markdown summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
Script finished. Script finished.

View File

@ -1,48 +1,67 @@
--- Log de Ejecución: x2_process_CAx.py --- --- Log de Ejecución: x2_process_CAx.py ---
Grupo: IO_adaptation Grupo: IO_adaptation
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Inicio: 2025-05-15 11:01:47 Inicio: 2025-06-06 16:34:01
Fin: 2025-05-15 11:01:52 Fin: 2025-06-06 16:34:09
Duración: 0:00:04.917359 Duración: 0:00:08.316649
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) --- --- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) ---
Using Working Directory for Output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Output JSON: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.hierarchical.json Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json
Output IO Debug Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md
Processing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
Pass 1: Found 203 InternalElement(s). Populating device dictionary... Pass 1: Found 363 InternalElement(s). Populating device dictionary...
Pass 2: Identifying PLCs and Networks (Refined v2)... Pass 2: Identifying PLCs and Networks (Refined v2)...
Identified Network: PROFIBUS_1 (442e4d1d-7d42-4d59-bd77-ec619f883907) Type: Profibus Identified Network: PN/IE_1 (fcbafb48-c53d-4af5-8143-794df99be4d6) Type: Unknown
Identified Network: ETHERNET_1 (26504433-7319-4b53-8f42-0ae24c9e88a2) Type: Ethernet/Profinet Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)... Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
Found 115 InternalLink(s). Found 103 InternalLink(s).
Mapping Device/Node 'E1' (NodeID:60d02ba8-54ea-4508-8a3a-986826e276c6, Addr:10.1.33.11) to Network 'ETHERNET_1' Mapping Device/Node 'E1' (NodeID:ffef8b7b-b6bf-46aa-b3b5-f5083c420563, Addr:10.1.30.11) to Network 'PN/IE_1'
--> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11) Mapping Device/Node 'IE1' (NodeID:221c248d-83d8-464c-9425-c949086b34e7, Addr:10.1.30.58) to Network 'PN/IE_1'
Mapping Device/Node 'P1' (NodeID:8cf403ec-810d-43de-826a-aa447f887ee3, Addr:1) to Network 'PROFIBUS_1' Mapping Device/Node 'IE1' (NodeID:5c3bf795-ecec-47e7-a962-4d54c95b9887, Addr:10.1.30.59) to Network 'PN/IE_1'
--> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1) Mapping Device/Node 'IE1' (NodeID:4100c829-d889-489a-971c-a69207333e2f, Addr:10.1.30.60) to Network 'PN/IE_1'
Mapping Device/Node 'PB1' (NodeID:b618b2b1-9ea8-41c1-a87e-fb0a3627a51d, Addr:12) to Network 'PROFIBUS_1' Mapping Device/Node 'IE1' (NodeID:a6aed467-1a4c-4a5a-bf3d-e7ce46e94a9f, Addr:10.1.30.61) to Network 'PN/IE_1'
Mapping Device/Node 'PB1' (NodeID:4e5d84a4-eb22-4d1f-8143-fc4d770eb2e7, Addr:20) to Network 'PROFIBUS_1' Mapping Device/Node 'IE1' (NodeID:b3d71b04-f99a-4af8-aa69-2ca02a30d391, Addr:10.1.30.62) to Network 'PN/IE_1'
Mapping Device/Node 'PB1' (NodeID:e6f362b4-398b-4854-b15b-7435745e4650, Addr:21) to Network 'PROFIBUS_1' Mapping Device/Node 'IE1' (NodeID:eb59daf8-f59c-435e-bfe4-8e7afd0937bd, Addr:10.1.30.63) to Network 'PN/IE_1'
Mapping Device/Node 'PB1' (NodeID:1a5422d6-c2bf-4a1c-8cf9-7b89fbaf4090, Addr:22) to Network 'PROFIBUS_1' Mapping Device/Node 'IE1' (NodeID:3e931f82-08a5-44de-89a5-b907f5aaeb24, Addr:10.1.30.64) to Network 'PN/IE_1'
Mapping Device/Node 'PB1' (NodeID:6b3de492-236f-4894-9bcf-3ee6c851c230, Addr:10) to Network 'PROFIBUS_1' Mapping Device/Node 'IE1' (NodeID:f673d611-7a73-4a8e-912d-ea16c294d51b, Addr:10.1.30.65) to Network 'PN/IE_1'
Mapping Device/Node 'PB1' (NodeID:bf8c18a3-aa60-4106-b779-afff78cdac47, Addr:8) to Network 'PROFIBUS_1' Mapping Device/Node 'IE1' (NodeID:09a2569a-4948-4830-9dd8-315ae638e391, Addr:10.1.30.66) to Network 'PN/IE_1'
Mapping Device/Node 'PB1' (NodeID:5e2810b0-4018-4747-bba9-19b8f9b14994, Addr:40) to Network 'PROFIBUS_1' Mapping Device/Node 'IE1' (NodeID:719a9643-c903-454e-8325-0f03cf871c35, Addr:10.1.30.31) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:ef8c3829-2363-43b3-ae20-7e903c2517a5, Addr:10.1.30.32) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7cece978-67aa-4707-b23c-375ebddfc2bc, Addr:10.1.30.170) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:ad86d223-8f60-41c0-8208-c88d68e42154, Addr:10.1.30.33) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:fa86bb0c-6142-41ce-ba6c-565e1e1f810e, Addr:10.1.30.34) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:441a0b56-5830-4595-ab8d-b073b6db8545, Addr:10.1.30.35) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:8dbc0c6b-a8f5-491c-832b-4af0757c259d, Addr:10.1.30.36) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:d06cdd21-62c3-4cff-9389-12a4e21869a2, Addr:10.1.30.40) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:f63db18d-dc19-48b8-afaa-be557db4d13e, Addr:10.1.30.44) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:21589a71-d4dd-4534-b457-deb72f3f7660, Addr:10.1.30.41) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:230ddec2-fa03-44d9-8350-0c0eeb463400, Addr:10.1.30.42) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b2647d59-ed85-44c5-813f-167c41ea1ca1, Addr:10.1.30.43) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:9933a2a3-47db-448a-a724-ed583d5994bb, Addr:10.1.30.37) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:9b45edaa-0640-4e86-ba59-1b991c1a85ca, Addr:10.1.30.45) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:a5fbeadb-a8ab-42e0-b58f-296e44633ce5, Addr:10.1.30.46) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:67d1d9ad-cf1d-43aa-9dc4-9e574cda5e5b, Addr:10.1.30.47) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:08689cb8-1b00-403f-bc20-c5911a20e655, Addr:10.1.30.48) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:a06a0e94-b651-4510-bbe0-8a0c3b717283, Addr:10.1.30.49) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:cf339d67-1758-4462-85f0-9e6a002d1c3c, Addr:10.1.30.70) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:d80a9fcf-5e0d-45a9-b617-362a9cb4121a, Addr:10.1.30.71) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7c28d11a-9673-4957-931d-8840a589da85, Addr:10.1.30.72) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:ad792043-fdb8-4695-a10a-aa2cad996cf0, Addr:10.1.30.74) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b30940b8-6cb5-4143-854f-9b569f081703, Addr:10.1.30.73) to Network 'PN/IE_1'
Data extraction and structuring complete. Data extraction and structuring complete.
Generating JSON output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.hierarchical.json Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json
JSON data written successfully. JSON data written successfully.
IO upward debug tree written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md IO upward debug tree written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md
Found 1 PLC(s). Generating individual hardware trees... Found 1 PLC(s). Generating individual hardware trees...
Generating Hardware Tree for PLC 'PLC' (ID: a48e038f-0bcc-4b48-8373-033da316c62b) at: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md Generating Hardware Tree for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md
Markdown tree summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md Markdown tree summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md
IO summary table written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Hardware.md
IO summary table generated in separate file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Hardware.md
Script finished. Script finished.

View File

@ -8,5 +8,5 @@
"ObsideanProjectsBase": "\\04-SIDEL" "ObsideanProjectsBase": "\\04-SIDEL"
}, },
"level3": {}, "level3": {},
"working_directory": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2" "working_directory": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia"
} }

View File

@ -1,6 +1,7 @@
{ {
"path": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2", "path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia",
"history": [ "history": [
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia",
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2" "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2"
] ]
} }

View File

@ -17,7 +17,12 @@ sys.path.append(script_root)
from backend.script_utils import load_configuration from backend.script_utils import load_configuration
# --- Configuration --- # --- Configuration ---
TIA_PORTAL_VERSION = "18.0" # Target TIA Portal version # Supported TIA Portal versions mapping (extension -> version)
SUPPORTED_TIA_VERSIONS = {
".ap18": "18.0",
".ap19": "19.0",
".ap20": "20.0"
}
# --- TIA Scripting Import Handling --- # --- TIA Scripting Import Handling ---
# (Same import handling as the previous script) # (Same import handling as the previous script)
@ -40,18 +45,43 @@ except Exception as e:
# --- Functions --- # --- Functions ---
def get_supported_filetypes():
"""Returns the supported file types for TIA Portal projects."""
filetypes = []
for ext, version in SUPPORTED_TIA_VERSIONS.items():
version_major = version.split('.')[0]
filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}"))
# Add option to show all supported files
all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()])
filetypes.insert(0, ("All TIA Portal Projects", all_extensions))
return filetypes
def detect_tia_version(project_file_path):
"""Detects TIA Portal version based on file extension."""
file_path = Path(project_file_path)
file_extension = file_path.suffix.lower()
if file_extension in SUPPORTED_TIA_VERSIONS:
detected_version = SUPPORTED_TIA_VERSIONS[file_extension]
print(f"Detected TIA Portal version: {detected_version} (from extension {file_extension})")
return detected_version
else:
print(f"WARNING: Unrecognized file extension '{file_extension}'. Supported extensions: {list(SUPPORTED_TIA_VERSIONS.keys())}")
# Default to version 18.0 for backward compatibility
print("Defaulting to TIA Portal V18.0")
return "18.0"
def select_project_file(): def select_project_file():
"""Opens a dialog to select a TIA Portal project file.""" """Opens a dialog to select a TIA Portal project file."""
root = tk.Tk() root = tk.Tk()
root.withdraw() root.withdraw()
file_path = filedialog.askopenfilename( file_path = filedialog.askopenfilename(
title="Select TIA Portal Project File", title="Select TIA Portal Project File",
filetypes=[ filetypes=get_supported_filetypes(),
(
f"TIA Portal V{TIA_PORTAL_VERSION} Projects",
f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}",
)
],
) )
root.destroy() root.destroy()
if not file_path: if not file_path:
@ -242,6 +272,9 @@ if __name__ == "__main__":
print(f"\nSelected Project: {project_file}") print(f"\nSelected Project: {project_file}")
print(f"Using Output Directory (Working Directory): {output_dir}") print(f"Using Output Directory (Working Directory): {output_dir}")
# 2. Detect TIA Portal version from project file
tia_version = detect_tia_version(project_file)
# Define output file names using Path object # Define output file names using Path object
project_path = Path(project_file) project_path = Path(project_file)
project_base_name = project_path.stem # Get filename without extension project_base_name = project_path.stem # Get filename without extension
@ -260,15 +293,15 @@ if __name__ == "__main__":
cax_export_successful = False cax_export_successful = False
try: try:
# 2. Connect to TIA Portal # 3. Connect to TIA Portal with detected version
print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...") print(f"\nConnecting to TIA Portal V{tia_version}...")
portal_instance = ts.open_portal( portal_instance = ts.open_portal(
version=TIA_PORTAL_VERSION, version=tia_version,
portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface, portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface,
) )
print("Connected.") print("Connected.")
# 3. Open Project # 4. Open Project
print( print(
f"Opening project: {project_path.name}..." f"Opening project: {project_path.name}..."
) # Use Path object's name attribute ) # Use Path object's name attribute
@ -282,7 +315,7 @@ if __name__ == "__main__":
raise Exception("Failed to open or get the specified project.") raise Exception("Failed to open or get the specified project.")
print("Project opened.") print("Project opened.")
# 4. Export CAx Data (Project Level) # 5. Export CAx Data (Project Level)
print(f"Exporting CAx data for the project to {aml_file}...") print(f"Exporting CAx data for the project to {aml_file}...")
# Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice) # Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice)
output_dir.mkdir(parents=True, exist_ok=True) output_dir.mkdir(parents=True, exist_ok=True)
@ -322,7 +355,7 @@ if __name__ == "__main__":
except Exception as close_ex: except Exception as close_ex:
print(f"Error during TIA Portal cleanup: {close_ex}") print(f"Error during TIA Portal cleanup: {close_ex}")
# 5. Parse AML and Generate Markdown (only if export was successful) # 6. Parse AML and Generate Markdown (only if export was successful)
if cax_export_successful: if cax_export_successful:
if aml_file.exists(): # Use Path object's exists() method if aml_file.exists(): # Use Path object's exists() method
parse_aml_to_markdown(aml_file, md_file) parse_aml_to_markdown(aml_file, md_file)

View File

@ -0,0 +1,33 @@
# Grupo de Ejemplo
Este es un archivo markdown de prueba para el **Launcher GUI**.
## Características
- Soporte para archivos `.md`
- Renderizado con markdown-it
- Visualización en modal
## Símbolos Unicode
Aquí tienes algunos símbolos que pueden causar problemas de encoding:
- Flecha derecha: →
- Flecha izquierda: ←
- Flecha doble: ⇒
- Caracteres especiales: ñ, á, é, í, ó, ú
- Emojis: 🚀 📄 ⚙️
## Código de Ejemplo
```python
def test_unicode():
print("Prueba de unicode → éxito")
return "Todo funcionando ✅"
```
## Lista de Verificación
- [x] Encoding UTF-8
- [x] Renderizado markdown
- [ ] Pruebas adicionales

View File

@ -1,47 +1,199 @@
{ {
"history": [ "history": [
{ {
"id": "03b026d2", "id": "6d4e8908",
"group_id": "1", "group_id": "1",
"script_name": "calc.py", "script_name": "calc.py",
"executed_date": "2025-06-03T12:09:48.856960Z", "executed_date": "2025-06-05T19:00:05.863426Z",
"arguments": [], "arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2", "working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting", "python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running", "status": "running",
"pid": 47056 "pid": 14212,
"execution_time": null
}, },
{ {
"id": "b27ada90", "id": "bc11fb82",
"group_id": "1",
"script_name": "calc.py",
"executed_date": "2025-06-05T18:55:33.372642Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 30456,
"execution_time": null
},
{
"id": "7dda414a",
"group_id": "1",
"script_name": "calc.py",
"executed_date": "2025-06-05T18:55:25.022607Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 31800,
"execution_time": null
},
{
"id": "ca0fdba4",
"group_id": "1",
"script_name": "calc.py",
"executed_date": "2025-06-05T18:55:20.165639Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 27436,
"execution_time": null
},
{
"id": "8eeccfaf",
"group_id": "1",
"script_name": "calc.py",
"executed_date": "2025-06-05T18:36:56.208761Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 11436,
"execution_time": null
},
{
"id": "be30fe5c",
"group_id": "1",
"script_name": "calc.py",
"executed_date": "2025-06-05T11:40:29.568449Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 34348,
"execution_time": null
},
{
"id": "de046db1",
"group_id": "2", "group_id": "2",
"script_name": "main.py", "script_name": "main.py",
"executed_date": "2025-06-03T12:09:12.887585Z", "executed_date": "2025-06-04T22:07:51.493125Z",
"arguments": [], "arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp", "working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting", "python_env": "tia_scripting",
"status": "running", "executable_type": "python.exe",
"pid": 18248 "status": "success",
"pid": 20460,
"execution_time": 37.787662
}, },
{ {
"id": "fb227680", "id": "76b18be0",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-04T22:06:04.365756Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 1036,
"execution_time": null
},
{
"id": "d3ad9738",
"group_id": "1", "group_id": "1",
"script_name": "calc.py", "script_name": "calc.py",
"executed_date": "2025-06-03T12:08:27.391884Z", "executed_date": "2025-06-04T18:56:27.720254Z",
"arguments": [], "arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2", "working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting", "python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running", "status": "running",
"pid": 38664 "pid": 43496,
"execution_time": null
}, },
{ {
"id": "f0b71d84", "id": "b7796abb",
"group_id": "1", "group_id": "1",
"script_name": "calc.py", "script_name": "calc.py",
"executed_date": "2025-06-03T11:48:50.783603Z", "executed_date": "2025-06-04T18:56:17.950282Z",
"arguments": [], "arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2", "working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "python.exe",
"status": "success",
"pid": 36312,
"execution_time": 8.35251
},
{
"id": "c29e9b02",
"group_id": "2",
"script_name": "main.py",
"executed_date": "2025-06-04T12:20:30.402310Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running", "status": "running",
"pid": 29560 "pid": 31416,
"execution_time": null
},
{
"id": "2a2caf05",
"group_id": "1",
"script_name": "test_symbolic.py",
"executed_date": "2025-06-04T11:15:06.230442Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "python.exe",
"status": "success",
"pid": 15052,
"execution_time": 3.768344
},
{
"id": "5e008836",
"group_id": "1",
"script_name": "test_symbolic.py",
"executed_date": "2025-06-04T11:15:00.297979Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "pythonw.exe",
"status": "running",
"pid": 47804,
"execution_time": null
},
{
"id": "b7b7f4da",
"group_id": "1",
"script_name": "calc.py",
"executed_date": "2025-06-04T11:13:44.475321Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "python.exe",
"status": "error",
"pid": 41924,
"execution_time": 27.585175
},
{
"id": "a8bf1045",
"group_id": "1",
"script_name": "calc.py",
"executed_date": "2025-06-04T11:13:24.997521Z",
"arguments": [],
"working_directory": "D:/Proyectos/Scripts/Calcv2",
"python_env": "tia_scripting",
"executable_type": "python.exe",
"status": "error",
"pid": 29848,
"execution_time": 8.158545
} }
], ],
"settings": { "settings": {

View File

@ -47,8 +47,8 @@
"display_name": "test_symbolic", "display_name": "test_symbolic",
"description": "", "description": "",
"long_description": "", "long_description": "",
"hidden": true, "hidden": false,
"updated_date": "2025-06-03T12:06:00.100424Z" "updated_date": "2025-06-03T13:44:26.045048Z"
}, },
"1_tl_bracket_parser.py": { "1_tl_bracket_parser.py": {
"display_name": "tl_bracket_parser", "display_name": "tl_bracket_parser",
@ -77,7 +77,133 @@
"long_description": "## Descripción\n\nSistema de Álgebra Computacional (CAS) que combina SymPy con clases especializadas para networking, programación y análisis numérico. Incluye sistema de tipos dinámico con auto-descubrimiento.\n\n## Características Principales\n\n- **Motor SymPy completo**: Cálculo simbólico y numérico integrado\n- **Sistema de tipos dinámico**: Auto-descubrimiento desde `custom_types/`\n- **Sintaxis simplificada**: `Tipo[args]` en lugar de `Tipo(\"args\")`\n- **Detección automática de ecuaciones**: Sin sintaxis especial\n- **Contexto limpio por evaluación**: Cada modificación evalúa todo desde cero, garantizando comportamiento predecible\n- **Resultados interactivos**: Elementos clickeables para plots, matrices y listas\n- **Autocompletado inteligente**: Basado en tipos registrados dinámicamente", "long_description": "## Descripción\n\nSistema de Álgebra Computacional (CAS) que combina SymPy con clases especializadas para networking, programación y análisis numérico. Incluye sistema de tipos dinámico con auto-descubrimiento.\n\n## Características Principales\n\n- **Motor SymPy completo**: Cálculo simbólico y numérico integrado\n- **Sistema de tipos dinámico**: Auto-descubrimiento desde `custom_types/`\n- **Sintaxis simplificada**: `Tipo[args]` en lugar de `Tipo(\"args\")`\n- **Detección automática de ecuaciones**: Sin sintaxis especial\n- **Contexto limpio por evaluación**: Cada modificación evalúa todo desde cero, garantizando comportamiento predecible\n- **Resultados interactivos**: Elementos clickeables para plots, matrices y listas\n- **Autocompletado inteligente**: Basado en tipos registrados dinámicamente",
"hidden": false, "hidden": false,
"updated_date": "2025-06-03T12:07:20.450126Z" "updated_date": "2025-06-03T12:07:20.450126Z"
},
"2_main.py": {
"display_name": "Simulador ADAM",
"description": "Simulador - Gateway - Sniffer",
"long_description": "",
"hidden": false,
"updated_date": "2025-06-03T12:34:27.531689Z"
},
"2_config_manager.py": {
"display_name": "config_manager",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:34:33.310121Z"
},
"2_connection_manager.py": {
"display_name": "connection_manager",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:34:36.597956Z"
},
"2_maselli_app.py": {
"display_name": "maselli_app",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:34:39.560628Z"
},
"2_protocol_handler.py": {
"display_name": "protocol_handler",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:34:42.028505Z"
},
"2_utils.py": {
"display_name": "utils",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:34:44.270726Z"
},
"3_google_api_key.py": {
"display_name": "google_api_key",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:35:47.564659Z"
},
"3_openai_api_key.py": {
"display_name": "openai_api_key",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:35:55.946088Z"
},
"3_test.py": {
"display_name": "test",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:06.694912Z"
},
"3_translation_config.py": {
"display_name": "translation_config",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:02.818575Z"
},
"3_x1_importar_to_master.py": {
"display_name": "x1_importar_to_master",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:11.404143Z"
},
"3_x2_master_export2translate.py": {
"display_name": "x2_master_export2translate",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:13.715080Z"
},
"3_x3_llm_auto_translate.py": {
"display_name": "x3_llm_auto_translate",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:17.369339Z"
},
"3_x4B_integrate_manual_translates_to_master.py": {
"display_name": "x4B_integrate_manual_translates_to_master",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:19.661939Z"
},
"3_x4_integrate_translates_to_master.py": {
"display_name": "x4_integrate_translates_to_master",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:21.890504Z"
},
"3_x5_complete_empty_cells_master.py": {
"display_name": "x5_complete_empty_cells_master",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:24.140382Z"
},
"3_x6_update_from_master.py": {
"display_name": "x6_update_from_master",
"description": "",
"long_description": "",
"hidden": true,
"updated_date": "2025-06-03T12:36:30.996433Z"
},
"3_menu_pasos_traduccion.py": {
"display_name": "Menu para Traducir x1-x6",
"description": "Menu para Traducir Textos de Siemens o Allenbradley",
"long_description": "",
"hidden": false,
"updated_date": "2025-06-03T12:37:14.675034Z"
} }
}, },
"updated_date": "2025-06-03T12:07:20.450126Z" "updated_date": "2025-06-03T13:44:26.045048Z"
} }

View File

@ -22,6 +22,17 @@
"directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp", "directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp",
"created_date": "2025-06-03T11:57:31.622922Z", "created_date": "2025-06-03T11:57:31.622922Z",
"updated_date": "2025-06-03T12:08:15.119223Z" "updated_date": "2025-06-03T12:08:15.119223Z"
},
{
"id": "3",
"name": "Traducir Textos usando LLM",
"description": "",
"category": "Otros",
"version": "1.0",
"python_env": "tia_scripting",
"directory": "D:/Proyectos/Scripts/HMI Translate",
"created_date": "2025-06-03T12:31:19.529046Z",
"updated_date": "2025-06-03T12:44:24.651659Z"
} }
], ],
"categories": { "categories": {

View File

@ -1,6 +1,82 @@
[12:09:48] Ejecutando script GUI: calc.py [16:30:18] Iniciando ejecución de x1_export_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia...
[12:09:48] Entorno Python: tia_scripting [16:30:19] --- TIA Portal Project CAx Exporter and Analyzer ---
[12:09:48] Comando: C:\Users\migue\miniconda3\envs\tia_scripting\python.exe D:/Proyectos/Scripts/Calcv2\calc.py [16:30:24] Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19
[12:09:48] Directorio: D:/Proyectos/Scripts/Calcv2 [16:30:24] Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
[12:09:48] Script GUI ejecutado con PID: 47056 [16:30:24] Detected TIA Portal version: 19.0 (from extension .ap19)
[12:09:48] ID de ejecución: 03b026d2 [16:30:24] Will export CAx data to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
[16:30:24] Will generate summary to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
[16:30:24] Export log file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.log
[16:30:24] Connecting to TIA Portal V19.0...
[16:30:25] 2025-06-06 16:30:25,861 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
[16:30:25] 2025-06-06 16:30:25,895 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface
[16:32:34] Connected.
[16:32:34] Opening project: 98050_PLC_01.ap19...
[16:32:34] 2025-06-06 16:32:34,404 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject - Open project... D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_01\98050_PLC_01.ap19
[16:33:05] Project opened.
[16:33:05] Exporting CAx data for the project to D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml...
[16:33:52] CAx data exported successfully.
[16:33:52] Closing TIA Portal...
[16:33:52] 2025-06-06 16:33:52,008 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal ClosePortal - Close TIA Portal
[16:33:52] TIA Portal closed.
[16:33:52] Parsing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
[16:33:52] Markdown summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
[16:33:52] Script finished.
[16:33:55] Ejecución de x1_export_CAx.py finalizada (success). Duración: 0:03:37.052535.
[16:33:55] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x1_export_CAx.txt
[16:34:01] Iniciando ejecución de x2_process_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia...
[16:34:01] --- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) ---
[16:34:01] Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
[16:34:09] Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
[16:34:09] Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
[16:34:09] Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json
[16:34:09] Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md
[16:34:09] Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
[16:34:09] Pass 1: Found 363 InternalElement(s). Populating device dictionary...
[16:34:09] Pass 2: Identifying PLCs and Networks (Refined v2)...
[16:34:09] Identified Network: PN/IE_1 (fcbafb48-c53d-4af5-8143-794df99be4d6) Type: Unknown
[16:34:09] Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
[16:34:09] Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
[16:34:09] Found 103 InternalLink(s).
[16:34:09] Mapping Device/Node 'E1' (NodeID:ffef8b7b-b6bf-46aa-b3b5-f5083c420563, Addr:10.1.30.11) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:221c248d-83d8-464c-9425-c949086b34e7, Addr:10.1.30.58) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:5c3bf795-ecec-47e7-a962-4d54c95b9887, Addr:10.1.30.59) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:4100c829-d889-489a-971c-a69207333e2f, Addr:10.1.30.60) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:a6aed467-1a4c-4a5a-bf3d-e7ce46e94a9f, Addr:10.1.30.61) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:b3d71b04-f99a-4af8-aa69-2ca02a30d391, Addr:10.1.30.62) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:eb59daf8-f59c-435e-bfe4-8e7afd0937bd, Addr:10.1.30.63) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:3e931f82-08a5-44de-89a5-b907f5aaeb24, Addr:10.1.30.64) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:f673d611-7a73-4a8e-912d-ea16c294d51b, Addr:10.1.30.65) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:09a2569a-4948-4830-9dd8-315ae638e391, Addr:10.1.30.66) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:719a9643-c903-454e-8325-0f03cf871c35, Addr:10.1.30.31) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:ef8c3829-2363-43b3-ae20-7e903c2517a5, Addr:10.1.30.32) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:7cece978-67aa-4707-b23c-375ebddfc2bc, Addr:10.1.30.170) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:ad86d223-8f60-41c0-8208-c88d68e42154, Addr:10.1.30.33) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:fa86bb0c-6142-41ce-ba6c-565e1e1f810e, Addr:10.1.30.34) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:441a0b56-5830-4595-ab8d-b073b6db8545, Addr:10.1.30.35) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:8dbc0c6b-a8f5-491c-832b-4af0757c259d, Addr:10.1.30.36) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:d06cdd21-62c3-4cff-9389-12a4e21869a2, Addr:10.1.30.40) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:f63db18d-dc19-48b8-afaa-be557db4d13e, Addr:10.1.30.44) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:21589a71-d4dd-4534-b457-deb72f3f7660, Addr:10.1.30.41) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:230ddec2-fa03-44d9-8350-0c0eeb463400, Addr:10.1.30.42) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:b2647d59-ed85-44c5-813f-167c41ea1ca1, Addr:10.1.30.43) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:9933a2a3-47db-448a-a724-ed583d5994bb, Addr:10.1.30.37) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:9b45edaa-0640-4e86-ba59-1b991c1a85ca, Addr:10.1.30.45) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:a5fbeadb-a8ab-42e0-b58f-296e44633ce5, Addr:10.1.30.46) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:67d1d9ad-cf1d-43aa-9dc4-9e574cda5e5b, Addr:10.1.30.47) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:08689cb8-1b00-403f-bc20-c5911a20e655, Addr:10.1.30.48) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:a06a0e94-b651-4510-bbe0-8a0c3b717283, Addr:10.1.30.49) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:cf339d67-1758-4462-85f0-9e6a002d1c3c, Addr:10.1.30.70) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:d80a9fcf-5e0d-45a9-b617-362a9cb4121a, Addr:10.1.30.71) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:7c28d11a-9673-4957-931d-8840a589da85, Addr:10.1.30.72) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:ad792043-fdb8-4695-a10a-aa2cad996cf0, Addr:10.1.30.74) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:b30940b8-6cb5-4143-854f-9b569f081703, Addr:10.1.30.73) to Network 'PN/IE_1'
[16:34:09] Data extraction and structuring complete.
[16:34:09] Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json
[16:34:09] JSON data written successfully.
[16:34:09] IO upward debug tree written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md
[16:34:09] Found 1 PLC(s). Generating individual hardware trees...
[16:34:09] Generating Hardware Tree for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md
[16:34:09] Markdown tree summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md
[16:34:09] Script finished.
[16:34:09] Ejecución de x2_process_CAx.py finalizada (success). Duración: 0:00:08.316649.
[16:34:09] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x2_process_CAx.txt

View File

@ -2,6 +2,8 @@ import os
import json import json
import subprocess import subprocess
import sys import sys
import threading
import time
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from datetime import datetime from datetime import datetime
import uuid import uuid
@ -219,45 +221,57 @@ class LauncherManager:
def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
"""Obtener scripts de un grupo específico con metadatos""" """Obtener scripts de un grupo específico con metadatos"""
try: try:
print(f"[DEBUG] Loading scripts for group: {group_id}")
group = self.get_launcher_group(group_id) group = self.get_launcher_group(group_id)
if not group: if not group:
print(f"[DEBUG] Group {group_id} not found")
return [] return []
directory = group["directory"] directory = group["directory"]
print(f"[DEBUG] Group directory: {directory}")
if not os.path.isdir(directory): if not os.path.isdir(directory):
print(f"[DEBUG] Directory {directory} does not exist or is not a directory")
return [] return []
# Cargar metadatos de scripts # Cargar metadatos de scripts
script_metadata = self._load_script_metadata() script_metadata = self._load_script_metadata()
scripts = [] scripts = []
for file in os.listdir(directory): python_files = [f for f in os.listdir(directory) if f.endswith('.py') and not f.startswith('_')]
if file.endswith('.py') and not file.startswith('_'): print(f"[DEBUG] Found {len(python_files)} Python files: {python_files}")
script_path = os.path.join(directory, file)
if os.path.isfile(script_path):
# Clave para metadatos
metadata_key = f"{group_id}_{file}"
metadata = script_metadata.get(metadata_key, {})
# Verificar si está oculto
if metadata.get("hidden", False):
continue
scripts.append({
"name": file,
"display_name": metadata.get("display_name", file[:-3]),
"description": metadata.get("description", ""),
"long_description": metadata.get("long_description", ""),
"path": script_path,
"size": os.path.getsize(script_path),
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
"hidden": metadata.get("hidden", False)
})
for file in python_files:
script_path = os.path.join(directory, file)
if os.path.isfile(script_path):
# Clave para metadatos
metadata_key = f"{group_id}_{file}"
metadata = script_metadata.get(metadata_key, {})
# Verificar si está oculto
if metadata.get("hidden", False):
print(f"[DEBUG] Script {file} is hidden, skipping")
continue
script_info = {
"name": file,
"display_name": metadata.get("display_name", file[:-3]),
"description": metadata.get("description", ""),
"long_description": metadata.get("long_description", ""),
"path": script_path,
"size": os.path.getsize(script_path),
"modified": datetime.fromtimestamp(os.path.getmtime(script_path)).isoformat(),
"hidden": metadata.get("hidden", False)
}
scripts.append(script_info)
print(f"[DEBUG] Added script: {script_info['display_name']} ({file})")
print(f"[DEBUG] Returning {len(scripts)} scripts for group {group_id}")
return sorted(scripts, key=lambda x: x["display_name"]) return sorted(scripts, key=lambda x: x["display_name"])
except Exception as e: except Exception as e:
print(f"Error getting scripts for group {group_id}: {e}") print(f"Error getting scripts for group {group_id}: {e}")
import traceback
traceback.print_exc()
return [] return []
def get_all_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: def get_all_group_scripts(self, group_id: str) -> List[Dict[str, Any]]:
@ -374,8 +388,12 @@ class LauncherManager:
return [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}] return [{"name": "base", "display_name": "Base (Sistema)", "path": sys.executable}]
def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str], def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str],
broadcast_func) -> Dict[str, Any]: broadcast_func, working_dir: str = None, use_pythonw: bool = False) -> Dict[str, Any]:
"""Ejecutar script GUI con argumentos opcionales y entorno específico""" """Ejecutar script GUI con argumentos opcionales y entorno específico
Args:
use_pythonw: Si True, usa pythonw.exe (sin logging), si False usa python.exe (con logging)
"""
try: try:
group = self.get_launcher_group(group_id) group = self.get_launcher_group(group_id)
if not group: if not group:
@ -387,27 +405,149 @@ class LauncherManager:
# Determinar el ejecutable de Python a usar # Determinar el ejecutable de Python a usar
python_env = group.get("python_env", "base") python_env = group.get("python_env", "base")
python_executable = self._get_python_executable(python_env) python_executable = self._get_python_executable(python_env, use_pythonw)
# Construir comando # Determinar directorio de trabajo
cmd = [python_executable, script_path] + script_args if working_dir and os.path.isdir(working_dir):
working_dir = group["directory"] # Por defecto directorio del script exec_working_dir = working_dir
else:
exec_working_dir = group["directory"] # Por defecto directorio del script
# Configurar variables de entorno para UTF-8
env = os.environ.copy()
env['PYTHONIOENCODING'] = 'utf-8'
env['PYTHONLEGACYWINDOWSSTDIO'] = '0'
# Variables adicionales para encoding
env['LANG'] = 'en_US.UTF-8'
env['LC_ALL'] = 'en_US.UTF-8'
env['PYTHONUNBUFFERED'] = '1'
# Forzar UTF-8 en consola de Windows
if sys.platform == "win32":
env['PYTHONUTF8'] = '1'
env['PYTHONLEGACYWINDOWSFSENCODING'] = '0'
# Construir comando con flag UTF-8
if use_pythonw:
cmd = [python_executable, script_path] + script_args
else:
# Para python.exe, agregar flag UTF-8 explícitamente
if python_executable.endswith('python.exe'):
cmd = [python_executable, '-X', 'utf8', script_path] + script_args
else:
cmd = [python_executable, script_path] + script_args
broadcast_func(f"Ejecutando script GUI: {script_name}") broadcast_func(f"Ejecutando script GUI: {script_name}")
broadcast_func(f"Entorno Python: {python_env}") broadcast_func(f"Entorno Python: {python_env}")
broadcast_func(f"Ejecutable: {'pythonw.exe (sin logging)' if use_pythonw else 'python.exe (con logging)'}")
broadcast_func(f"Comando: {' '.join(cmd)}") broadcast_func(f"Comando: {' '.join(cmd)}")
broadcast_func(f"Directorio: {working_dir}") broadcast_func(f"Directorio: {exec_working_dir}")
broadcast_func(f"Encoding: UTF-8 (PYTHONUTF8=1, PYTHONIOENCODING=utf-8)")
if '-X' in cmd and 'utf8' in cmd:
broadcast_func("Usando flag -X utf8 para forzar UTF-8")
broadcast_func("=" * 50)
# Ejecutar script # Ejecutar script
start_time = datetime.now() start_time = datetime.now()
process = subprocess.Popen(
cmd, if use_pythonw:
cwd=working_dir, # Con pythonw.exe: No capturar salida, solo ejecutar
stdout=subprocess.PIPE, try:
stderr=subprocess.PIPE, process = subprocess.Popen(
text=True, cmd,
creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0 cwd=exec_working_dir,
) env=env,
creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0
)
broadcast_func("Script GUI ejecutado sin logging (pythonw.exe)")
broadcast_func(f"PID: {process.pid}")
broadcast_func("=" * 50)
status = "running"
except subprocess.SubprocessError as e:
error_msg = f"Error ejecutando con pythonw.exe: {str(e)}"
broadcast_func(error_msg)
return {"status": "error", "message": error_msg}
else:
# Con python.exe: Capturar salida para logging
try:
process = subprocess.Popen(
cmd,
cwd=exec_working_dir,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # Combinar stderr con stdout
text=True,
encoding='utf-8', # Forzar UTF-8
universal_newlines=True,
bufsize=1 # Line buffered
)
# Leer salida en tiempo real
def read_output():
try:
for line in iter(process.stdout.readline, ''):
if line.strip(): # Solo enviar líneas no vacías
broadcast_func(line.rstrip())
process.stdout.close()
except Exception as e:
broadcast_func(f"Error leyendo salida: {str(e)}")
# Iniciar thread para leer salida
output_thread = threading.Thread(target=read_output, daemon=True)
output_thread.start()
# Esperar más tiempo para capturar salida inicial y detectar finalización
time.sleep(2)
# Verificar si el proceso sigue corriendo
poll_result = process.poll()
if poll_result is not None:
# El proceso terminó
execution_time = (datetime.now() - start_time).total_seconds()
# Esperar a que termine el thread de lectura
output_thread.join(timeout=1)
if poll_result == 0:
broadcast_func("=" * 50)
broadcast_func(f"Script completado exitosamente en {execution_time:.2f}s")
status = "success"
else:
broadcast_func("=" * 50)
broadcast_func(f"Script terminó con código de error: {poll_result}")
status = "error"
else:
# El proceso sigue corriendo (típico para GUIs)
broadcast_func("=" * 50)
broadcast_func(f"Script GUI iniciado correctamente (PID: {process.pid})")
broadcast_func("Nota: El script sigue ejecutándose en segundo plano")
status = "running"
# Iniciar un thread separado para monitorear la finalización
def monitor_completion():
try:
final_code = process.wait() # Esperar hasta que termine
end_time = datetime.now()
final_execution_time = (end_time - start_time).total_seconds()
# Actualizar el historial cuando termine
self._update_history_status(execution_id, final_code, final_execution_time)
if final_code == 0:
broadcast_func(f"[{end_time.strftime('%H:%M:%S')}] Script {script_name} completado exitosamente ({final_execution_time:.2f}s)")
else:
broadcast_func(f"[{end_time.strftime('%H:%M:%S')}] Script {script_name} terminó con error (código: {final_code})")
except Exception as e:
broadcast_func(f"Error monitoreando finalización: {str(e)}")
monitor_thread = threading.Thread(target=monitor_completion, daemon=True)
monitor_thread.start()
except subprocess.SubprocessError as e:
error_msg = f"Error ejecutando con python.exe: {str(e)}"
broadcast_func(error_msg)
return {"status": "error", "message": error_msg}
# Registrar en historial # Registrar en historial
execution_id = str(uuid.uuid4())[:8] execution_id = str(uuid.uuid4())[:8]
@ -417,13 +557,14 @@ class LauncherManager:
"script_name": script_name, "script_name": script_name,
"executed_date": start_time.isoformat() + "Z", "executed_date": start_time.isoformat() + "Z",
"arguments": script_args, "arguments": script_args,
"working_directory": working_dir, "working_directory": exec_working_dir,
"python_env": python_env, "python_env": python_env,
"status": "running", "executable_type": "pythonw.exe" if use_pythonw else "python.exe",
"pid": process.pid "status": status,
"pid": process.pid,
"execution_time": (datetime.now() - start_time).total_seconds() if status != "running" else None
}) })
broadcast_func(f"Script GUI ejecutado con PID: {process.pid}")
broadcast_func(f"ID de ejecución: {execution_id}") broadcast_func(f"ID de ejecución: {execution_id}")
return { return {
@ -438,9 +579,23 @@ class LauncherManager:
broadcast_func(error_msg) broadcast_func(error_msg)
return {"status": "error", "message": error_msg} return {"status": "error", "message": error_msg}
def _get_python_executable(self, env_name: str) -> str: def _get_python_executable(self, env_name: str, use_pythonw: bool = False) -> str:
"""Obtener el ejecutable de Python para un entorno específico""" """Obtener el ejecutable de Python para un entorno específico
Args:
env_name: Nombre del entorno Python
use_pythonw: Si True, usa pythonw.exe (sin consola), si False usa python.exe (con logging)
"""
if env_name == "base": if env_name == "base":
# Para el sistema base
base_dir = os.path.dirname(sys.executable)
if use_pythonw:
pythonw_path = os.path.join(base_dir, "pythonw.exe")
if os.path.exists(pythonw_path):
return pythonw_path
python_path = os.path.join(base_dir, "python.exe")
if os.path.exists(python_path):
return python_path
return sys.executable return sys.executable
# Buscar en entornos de Miniconda # Buscar en entornos de Miniconda
@ -453,11 +608,23 @@ class LauncherManager:
] ]
for base_path in miniconda_paths: for base_path in miniconda_paths:
env_path = os.path.join(base_path, "envs", env_name, "python.exe") if use_pythonw:
if os.path.exists(env_path): # Intentar pythonw.exe para GUI sin consola
return env_path env_pythonw_path = os.path.join(base_path, "envs", env_name, "pythonw.exe")
if os.path.exists(env_pythonw_path):
return env_pythonw_path
# Intentar python.exe para logging
env_python_path = os.path.join(base_path, "envs", env_name, "python.exe")
if os.path.exists(env_python_path):
return env_python_path
# Fallback al sistema # Fallback final al sistema
base_dir = os.path.dirname(sys.executable)
if use_pythonw:
pythonw_path = os.path.join(base_dir, "pythonw.exe")
if os.path.exists(pythonw_path):
return pythonw_path
return sys.executable return sys.executable
def _load_script_metadata(self) -> Dict[str, Any]: def _load_script_metadata(self) -> Dict[str, Any]:
@ -615,4 +782,237 @@ class LauncherManager:
json.dump(data, f, indent=2, ensure_ascii=False) json.dump(data, f, indent=2, ensure_ascii=False)
except Exception as e: except Exception as e:
print(f"Error cleaning up favorites for group {group_id}: {e}") print(f"Error cleaning up favorites for group {group_id}: {e}")
def _update_history_status(self, execution_id: str, final_code: int, final_execution_time: float):
"""Actualizar el estado del historial de ejecución"""
try:
with open(self.history_path, 'r', encoding='utf-8') as f:
data = json.load(f)
history = data.get("history", [])
for i, entry in enumerate(history):
if entry["id"] == execution_id:
history[i]["status"] = "success" if final_code == 0 else "error"
history[i]["execution_time"] = final_execution_time
break
data["history"] = history
with open(self.history_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error updating history status: {e}")
def focus_process(self, pid: int) -> Dict[str, str]:
"""Activar el foco de un proceso por su PID"""
try:
if sys.platform == "win32":
import ctypes
from ctypes import wintypes
# Funciones de Windows API
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
# Encontrar ventana por PID
def enum_windows_proc(hwnd, pid):
window_pid = wintypes.DWORD()
user32.GetWindowThreadProcessId(hwnd, ctypes.byref(window_pid))
if window_pid.value == pid:
# Traer ventana al frente
user32.SetForegroundWindow(hwnd)
user32.ShowWindow(hwnd, 9) # SW_RESTORE
return False # Detener enumeración
return True
# Enumeración de ventanas
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)
enum_proc = WNDENUMPROC(enum_windows_proc)
user32.EnumWindows(enum_proc, pid)
return {"status": "success", "message": f"Proceso {pid} activado"}
else:
return {"status": "error", "message": "Función solo disponible en Windows"}
except Exception as e:
return {"status": "error", "message": f"Error activando proceso: {str(e)}"}
def terminate_process(self, pid: int) -> Dict[str, str]:
"""Cerrar un proceso por su PID"""
try:
import psutil
process = psutil.Process(pid)
process_name = process.name()
# Intentar cerrar suavemente primero
process.terminate()
# Esperar un momento para que cierre
time.sleep(1)
# Si sigue corriendo, forzar cierre
if process.is_running():
process.kill()
return {"status": "success", "message": f"Proceso {process_name} (PID: {pid}) cerrado exitosamente"}
except ImportError:
# Fallback sin psutil
try:
if sys.platform == "win32":
subprocess.run(["taskkill", "/F", "/PID", str(pid)], check=True)
else:
subprocess.run(["kill", "-9", str(pid)], check=True)
return {"status": "success", "message": f"Proceso {pid} cerrado"}
except subprocess.CalledProcessError as e:
return {"status": "error", "message": f"Error cerrando proceso: {str(e)}"}
except Exception as e:
return {"status": "error", "message": f"Error: {str(e)}"}
def get_running_processes(self) -> List[Dict[str, Any]]:
"""Obtener lista de procesos en ejecución del historial"""
try:
history = self.get_history()
running_processes = []
for entry in history:
if entry.get("status") == "running" and entry.get("pid"):
try:
# Verificar si el proceso sigue corriendo
if sys.platform == "win32":
result = subprocess.run(
["tasklist", "/FI", f"PID eq {entry['pid']}", "/FO", "CSV"],
capture_output=True, text=True
)
if str(entry['pid']) in result.stdout:
running_processes.append(entry)
else:
# Linux/Mac
result = subprocess.run(
["ps", "-p", str(entry['pid'])],
capture_output=True, text=True
)
if result.returncode == 0:
running_processes.append(entry)
except Exception:
continue
return running_processes
except Exception as e:
print(f"Error getting running processes: {e}")
return []
def get_markdown_files(self, group_id: str) -> List[Dict[str, Any]]:
"""Obtener archivos Markdown de un grupo (root y subdirectorios hasta 10 archivos)"""
try:
print(f"[DEBUG] Looking for markdown files in group: {group_id}")
group = self.get_launcher_group(group_id)
if not group:
print(f"[DEBUG] Group {group_id} not found for markdown search")
return []
directory = group["directory"]
print(f"[DEBUG] Searching markdown files in directory: {directory}")
if not os.path.isdir(directory):
print(f"[DEBUG] Directory {directory} does not exist for markdown search")
return []
markdown_files = []
# Buscar en directorio root
all_files = os.listdir(directory)
print(f"[DEBUG] All files in directory: {all_files}")
for file in all_files:
if file.lower().endswith('.md'):
file_path = os.path.join(directory, file)
if os.path.isfile(file_path):
print(f"[DEBUG] Found markdown file: {file}")
markdown_files.append({
"name": file,
"display_name": file[:-3], # Sin extensión .md
"path": file_path,
"relative_path": file,
"level": 0, # Root level
"size": os.path.getsize(file_path),
"modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
})
else:
print(f"[DEBUG] {file} is not a file, skipping")
# Buscar en subdirectorios (máximo 1 nivel)
try:
for subdir in all_files:
subdir_path = os.path.join(directory, subdir)
if os.path.isdir(subdir_path) and (not subdir.startswith('.') or subdir.startswith('.doc')):
print(f"[DEBUG] Searching in subdirectory: {subdir}")
subdir_files = os.listdir(subdir_path)
for file in subdir_files:
if file.lower().endswith('.md'):
file_path = os.path.join(subdir_path, file)
if os.path.isfile(file_path):
print(f"[DEBUG] Found markdown file in subdir: {subdir}/{file}")
markdown_files.append({
"name": file,
"display_name": f"{subdir}/{file[:-3]}",
"path": file_path,
"relative_path": f"{subdir}/{file}",
"level": 1, # Subdirectory level
"size": os.path.getsize(file_path),
"modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
})
# Limitar a 10 archivos total
if len(markdown_files) >= 10:
break
if len(markdown_files) >= 10:
break
except PermissionError as e:
print(f"[DEBUG] Permission error accessing subdirectories: {e}")
# Ignorar subdirectorios sin permisos
pass
# Ordenar por modificación (más recientes primero) y limitar a 10
markdown_files.sort(key=lambda x: x["modified"], reverse=True)
print(f"[DEBUG] Found {len(markdown_files)} total markdown files")
return markdown_files[:10]
except Exception as e:
print(f"Error getting markdown files for group {group_id}: {e}")
import traceback
traceback.print_exc()
return []
def read_markdown_file(self, group_id: str, relative_path: str) -> Dict[str, Any]:
"""Leer contenido de un archivo Markdown"""
try:
group = self.get_launcher_group(group_id)
if not group:
return {"status": "error", "message": "Grupo no encontrado"}
file_path = os.path.join(group["directory"], relative_path)
# Verificar que el archivo existe y está dentro del directorio del grupo (seguridad)
if not os.path.isfile(file_path):
return {"status": "error", "message": "Archivo no encontrado"}
if not file_path.startswith(group["directory"]):
return {"status": "error", "message": "Acceso denegado"}
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return {
"status": "success",
"content": content,
"file_name": os.path.basename(file_path),
"file_path": relative_path,
"size": len(content),
"modified": datetime.fromtimestamp(os.path.getmtime(file_path)).isoformat()
}
except Exception as e:
return {"status": "error", "message": f"Error leyendo archivo: {str(e)}"}

View File

@ -14,3 +14,11 @@ pypandoc==1.15
Requests==2.32.3 Requests==2.32.3
siemens_tia_scripting==1.0.7 siemens_tia_scripting==1.0.7
sympy==1.13.3 sympy==1.13.3
pillow
pystray
# Nota: Para evitar problemas de encoding, configura estas variables de entorno:
# PYTHONIOENCODING=utf-8
# PYTHONLEGACYWINDOWSSTDIO=0
#
# O ejecuta Python con: python -X utf8 app.py

12
run_utf8.bat Normal file
View File

@ -0,0 +1,12 @@
@echo off
echo Configurando encoding UTF-8 para ParamManager...
set PYTHONIOENCODING=utf-8
set PYTHONLEGACYWINDOWSSTDIO=0
echo.
echo Variables de entorno configuradas:
echo PYTHONIOENCODING=%PYTHONIOENCODING%
echo PYTHONLEGACYWINDOWSSTDIO=%PYTHONLEGACYWINDOWSSTDIO%
echo.
echo Iniciando ParamManager...
python app.py
pause

View File

@ -221,8 +221,8 @@
} }
.group-icon-small { .group-icon-small {
width: 24px; width: 20px;
height: 24px; height: 20px;
border-radius: 4px; border-radius: 4px;
object-fit: cover; object-fit: cover;
border: 1px solid #E5E7EB; border: 1px solid #E5E7EB;
@ -234,7 +234,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: white; color: white;
font-size: 12px; font-size: 10px;
} }
/* Category Styles */ /* Category Styles */
@ -291,12 +291,10 @@
.favorite-star { .favorite-star {
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
color: #D1D5DB;
} }
.favorite-star:hover { .favorite-star:hover {
transform: scale(1.1); transform: scale(1.1);
color: #F59E0B;
} }
.favorite-star.active { .favorite-star.active {
@ -305,16 +303,11 @@
/* History Item */ /* History Item */
.history-item { .history-item {
padding: 1rem;
margin-bottom: 0.5rem;
border-radius: 0.5rem;
border: 1px solid #E5E7EB; border: 1px solid #E5E7EB;
border-radius: 6px; background: white;
padding: 0.75rem;
background: #F9FAFB;
transition: all 0.2s ease;
}
.history-item:hover {
background: #F3F4F6;
border-color: #D1D5DB;
} }
.history-item.success { .history-item.success {
@ -327,6 +320,19 @@
.history-item.running { .history-item.running {
border-left: 4px solid #3B82F6; border-left: 4px solid #3B82F6;
background: #F0F9FF;
}
/* Process control buttons */
.history-item button {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
transition: all 0.2s ease;
}
.history-item button:hover {
background-color: rgba(0, 0, 0, 0.1);
} }
/* Favorites Panel */ /* Favorites Panel */
@ -338,6 +344,19 @@
display: none; display: none;
} }
/* Favorite Cards - Diseño más compacto */
.favorites-card {
transition: all 0.2s ease;
border: 1px solid #FCD34D;
background: white;
}
.favorites-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-color: #F59E0B;
}
/* Group List Item */ /* Group List Item */
.group-list-item { .group-list-item {
display: flex; display: flex;
@ -533,4 +552,173 @@
.focus-visible:focus { .focus-visible:focus {
outline: 2px solid #3B82F6; outline: 2px solid #3B82F6;
outline-offset: 2px; outline-offset: 2px;
}
/* Description Modal Styles */
.prose {
color: #374151;
line-height: 1.75;
}
.prose h1,
.prose h2,
.prose h3,
.prose h4,
.prose h5,
.prose h6 {
color: #111827;
font-weight: 600;
margin-top: 1.5em;
margin-bottom: 0.5em;
}
.prose h1 {
font-size: 1.875rem;
border-bottom: 1px solid #E5E7EB;
padding-bottom: 0.5rem;
}
.prose h2 {
font-size: 1.5rem;
}
.prose h3 {
font-size: 1.25rem;
}
.prose p {
margin-bottom: 1rem;
}
.prose ul,
.prose ol {
margin-bottom: 1rem;
padding-left: 1.5rem;
}
.prose li {
margin-bottom: 0.25rem;
}
.prose code {
background-color: #F3F4F6;
padding: 0.125rem 0.25rem;
border-radius: 0.25rem;
font-size: 0.875em;
color: #EF4444;
}
.prose pre {
background-color: #F9FAFB;
border: 1px solid #E5E7EB;
border-radius: 0.5rem;
padding: 1rem;
overflow-x: auto;
margin: 1rem 0;
}
.prose pre code {
background: none;
padding: 0;
color: #374151;
}
.prose blockquote {
border-left: 4px solid #E5E7EB;
padding-left: 1rem;
font-style: italic;
color: #6B7280;
margin: 1rem 0;
}
.prose table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
.prose th,
.prose td {
border: 1px solid #E5E7EB;
padding: 0.5rem;
text-align: left;
}
.prose th {
background-color: #F9FAFB;
font-weight: 600;
}
.prose a {
color: #3B82F6;
text-decoration: underline;
}
.prose a:hover {
color: #1D4ED8;
}
/* Script Card Button Improvements */
.script-card .flex.gap-1 button {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
transition: all 0.2s ease;
}
.script-card .flex.gap-1 button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Markdown Files */
.markdown-file-card {
transition: all 0.2s ease;
border: 1px solid #E5E7EB;
}
.markdown-file-card:hover {
border-color: #3B82F6;
transform: translateY(-2px);
}
/* Responsive adjustments for favorites grid */
@media (max-width: 640px) {
.favorites-list.grid {
grid-template-columns: 1fr;
}
}
@media (min-width: 641px) and (max-width: 1023px) {
.favorites-list.grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.favorites-list.grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Button styles for favorite cards */
.favorites-card button {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
transition: all 0.2s ease;
border: none;
cursor: pointer;
}
.favorites-card button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Truncate text utilities */
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }

BIN
static/icons/cursor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
static/icons/vscode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -11,6 +11,7 @@ class LauncherManager {
this.currentFilter = 'all'; this.currentFilter = 'all';
this.currentEditingGroup = null; this.currentEditingGroup = null;
this.pythonEnvs = []; this.pythonEnvs = [];
this.markdownFiles = [];
} }
async init() { async init() {
@ -71,7 +72,7 @@ class LauncherManager {
const response = await fetch('/api/launcher-favorites'); const response = await fetch('/api/launcher-favorites');
const data = await response.json(); const data = await response.json();
this.favorites = new Set(data.favorites.map(f => `${f.group_id}_${f.script_name}`)); this.favorites = new Set(data.favorites.map(f => `${f.group_id}_${f.script_name}`));
this.renderFavorites(data.favorites); await this.renderFavorites(data.favorites);
} catch (error) { } catch (error) {
console.error('Error loading favorites:', error); console.error('Error loading favorites:', error);
} }
@ -118,9 +119,15 @@ class LauncherManager {
const selector = document.getElementById('launcher-group-select'); const selector = document.getElementById('launcher-group-select');
if (!selector) return; if (!selector) return;
// Guardar la selección actual
const currentSelection = this.getCurrentGroupSelection();
selector.innerHTML = '<option value="">-- Seleccionar Grupo --</option>'; selector.innerHTML = '<option value="">-- Seleccionar Grupo --</option>';
this.groups.forEach(group => { // Ordenar grupos alfabéticamente por nombre
const sortedGroups = [...this.groups].sort((a, b) => a.name.localeCompare(b.name));
sortedGroups.forEach(group => {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = group.id; option.value = group.id;
option.textContent = group.name; option.textContent = group.name;
@ -128,6 +135,32 @@ class LauncherManager {
option.dataset.description = group.description; option.dataset.description = group.description;
selector.appendChild(option); selector.appendChild(option);
}); });
// Restaurar la selección guardada
this.restoreGroupSelection(currentSelection);
}
// Nuevo método para obtener la selección actual del localStorage
getCurrentGroupSelection() {
const currentValue = document.getElementById('launcher-group-select')?.value;
return localStorage.getItem('launcher-selected-group') || currentValue || '';
}
// Nuevo método para restaurar la selección desde localStorage
restoreGroupSelection(groupId) {
const selector = document.getElementById('launcher-group-select');
if (!selector || !groupId) return;
// Verificar que el grupo aún existe
const groupExists = this.groups.some(g => g.id === groupId);
if (groupExists) {
selector.value = groupId;
// Cargar scripts del grupo restaurado automáticamente
this.loadLauncherScripts();
} else {
// Si el grupo ya no existe, limpiar localStorage
localStorage.removeItem('launcher-selected-group');
}
} }
renderCategoryFilter() { renderCategoryFilter() {
@ -152,26 +185,60 @@ class LauncherManager {
async loadLauncherScripts() { async loadLauncherScripts() {
const groupId = document.getElementById('launcher-group-select').value; const groupId = document.getElementById('launcher-group-select').value;
// Guardar la selección en localStorage
if (groupId) {
localStorage.setItem('launcher-selected-group', groupId);
} else {
localStorage.removeItem('launcher-selected-group');
}
if (!groupId) { if (!groupId) {
this.scripts = []; this.scripts = [];
this.markdownFiles = [];
this.renderScripts(); this.renderScripts();
this.renderMarkdownFiles();
this.updateManageScriptsButton(false); this.updateManageScriptsButton(false);
this.updateEditorButtons(false);
return; return;
} }
// Cargar scripts (parte crítica)
try { try {
const response = await fetch(`/api/launcher-scripts/${groupId}`); const response = await fetch(`/api/launcher-scripts/${groupId}`);
this.scripts = await response.json(); if (response.ok) {
this.currentGroup = this.groups.find(g => g.id === groupId); this.scripts = await response.json();
this.updateGroupIcon(); } else {
this.renderScripts(); console.error('Error loading scripts:', response.status, response.statusText);
this.updateManageScriptsButton(true); this.scripts = [];
}
} catch (error) { } catch (error) {
console.error('Error loading launcher scripts:', error); console.error('Error loading launcher scripts:', error);
this.scripts = []; this.scripts = [];
this.renderScripts();
this.updateManageScriptsButton(false);
} }
// Cargar archivos markdown (parte opcional)
try {
const markdownResponse = await fetch(`/api/launcher-markdown/${groupId}`);
if (markdownResponse.ok) {
const markdownData = await markdownResponse.json();
this.markdownFiles = markdownData.files || [];
} else {
console.warn('No markdown files available for group:', groupId);
this.markdownFiles = [];
}
} catch (error) {
console.warn('Error loading markdown files (non-critical):', error);
this.markdownFiles = [];
}
// Actualizar interfaz
this.currentGroup = this.groups.find(g => g.id === groupId);
this.updateGroupIcon();
this.renderScripts();
this.renderMarkdownFiles();
this.updateManageScriptsButton(this.scripts.length > 0);
this.updateEditorButtons(true); // Usar la nueva función
} }
updateManageScriptsButton(show) { updateManageScriptsButton(show) {
@ -181,6 +248,18 @@ class LauncherManager {
} }
} }
// Función actualizada para mostrar/ocultar ambos botones de editor
updateEditorButtons(show) {
const vscodeButton = document.getElementById('vscode-launcher-btn');
const cursorButton = document.getElementById('cursor-launcher-btn');
if (vscodeButton) {
vscodeButton.style.display = show ? 'inline-flex' : 'none'; // Usar inline-flex para iconos+texto si aplica
}
if (cursorButton) {
cursorButton.style.display = show ? 'inline-flex' : 'none'; // Usar inline-flex para iconos+texto si aplica
}
}
updateGroupIcon() { updateGroupIcon() {
const iconElement = document.getElementById('selected-group-icon'); const iconElement = document.getElementById('selected-group-icon');
if (!iconElement || !this.currentGroup) return; if (!iconElement || !this.currentGroup) return;
@ -230,7 +309,9 @@ class LauncherManager {
let filteredScripts = this.scripts; let filteredScripts = this.scripts;
if (this.currentFilter !== 'all' && this.currentGroup) { if (this.currentFilter !== 'all' && this.currentGroup) {
if (this.currentGroup.category !== this.currentFilter) { // Filtrar solo si la categoría del grupo no coincide con el filtro
const groupCategory = this.currentGroup.category;
if (groupCategory !== this.currentFilter) {
filteredScripts = []; filteredScripts = [];
} }
} }
@ -245,7 +326,7 @@ class LauncherManager {
card.innerHTML = ` card.innerHTML = `
<div class="flex justify-between items-start mb-2"> <div class="flex justify-between items-start mb-2">
<h4 class="font-medium text-gray-900">${script.display_name}</h4> <h4 class="font-medium text-gray-900">${script.display_name}</h4>
<button class="favorite-star ${isFavorite ? 'active' : ''}" <button class="favorite-star ${isFavorite ? 'active' : ''}"
onclick="launcherManager.toggleFavorite('${this.currentGroup.id}', '${script.name}')"> onclick="launcherManager.toggleFavorite('${this.currentGroup.id}', '${script.name}')">
</button> </button>
@ -253,15 +334,30 @@ class LauncherManager {
<p class="text-sm text-gray-600 mb-3 line-clamp-2">${script.description || 'Script: ' + script.name}</p> <p class="text-sm text-gray-600 mb-3 line-clamp-2">${script.description || 'Script: ' + script.name}</p>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="category-badge">${this.currentGroup.category}</span> <span class="category-badge">${this.currentGroup.category}</span>
<div class="space-x-2"> <div class="flex flex-col gap-1 text-xs">
<button class="text-blue-500 hover:underline text-sm" <div class="flex gap-1">
onclick="launcherManager.showArgsModal('${script.name}', '${script.display_name}')"> ${script.long_description && script.long_description.trim() ? `
Con Argumentos <button class="text-green-600 hover:underline"
</button> onclick="launcherManager.showDescriptionModal('${script.name}', '${this.escapeHtml(script.display_name)}')">
<button class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600" Descripción
onclick="launcherManager.executeScript('${script.name}')"> </button>` : ''}
Ejecutar <button class="text-blue-500 hover:underline"
</button> onclick="launcherManager.showArgsModal('${script.name}', '${this.escapeHtml(script.display_name)}')">
Con Argumentos
</button>
</div>
<div class="flex gap-1">
<button class="bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600 text-xs"
onclick="launcherManager.executeScript('${script.name}', [], null, false)"
title="Ejecutar con python.exe (muestra logs)">
🖥 Con Log
</button>
<button class="bg-green-500 text-white px-2 py-1 rounded hover:bg-green-600 text-xs"
onclick="launcherManager.executeScript('${script.name}', [], null, true)"
title="Ejecutar con pythonw.exe (sin ventana de consola)">
🚀 Sin Log
</button>
</div>
</div> </div>
</div> </div>
`; `;
@ -269,6 +365,111 @@ class LauncherManager {
}); });
} }
renderMarkdownFiles() {
const container = document.getElementById('markdown-files-section');
if (!container) return;
if (!this.currentGroup || this.markdownFiles.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'block';
const grid = document.getElementById('markdown-files-grid');
if (!grid) return;
grid.innerHTML = '';
this.markdownFiles.forEach(file => {
const card = document.createElement('div');
card.className = 'markdown-file-card bg-white border rounded-lg p-3 hover:shadow-md transition-shadow cursor-pointer';
card.innerHTML = `
<div class="flex items-start justify-between">
<div class="flex-1">
<h4 class="font-medium text-gray-900 text-sm">${file.display_name}</h4>
<p class="text-xs text-gray-500 mt-1">
📄 ${(file.size / 1024).toFixed(1)} KB
${file.level === 1 ? ' • Subdirectorio' : ''}
</p>
</div>
<span class="text-xs text-gray-400">${this.getTimeAgo(file.modified)}</span>
</div>
`;
card.onclick = () => this.openMarkdownViewer(file.relative_path, file.display_name);
grid.appendChild(card);
});
}
async openMarkdownViewer(relativePath, displayName) {
if (!this.currentGroup) return;
try {
const response = await fetch(`/api/launcher-markdown-content/${this.currentGroup.id}/${encodeURIComponent(relativePath)}`);
const result = await response.json();
if (result.status === 'success') {
const modal = document.getElementById('markdown-viewer-modal');
const titleElement = document.getElementById('markdown-viewer-title');
const pathElement = document.getElementById('markdown-viewer-path');
const contentElement = document.getElementById('markdown-viewer-content');
if (modal && titleElement && contentElement) {
titleElement.textContent = displayName;
pathElement.textContent = `Archivo: ${relativePath}`;
// Renderizar markdown
const md = window.markdownit();
contentElement.innerHTML = md.render(result.content);
modal.classList.remove('hidden');
}
} else {
alert(`Error: ${result.message}`);
}
} catch (error) {
console.error('Error loading markdown file:', error);
alert('Error cargando archivo Markdown');
}
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async showDescriptionModal(scriptName, displayName) {
if (!this.currentGroup) return;
try {
// Cargar metadatos del script para obtener la descripción larga
const response = await fetch(`/api/launcher-script-metadata/${this.currentGroup.id}/${scriptName}`);
const metadata = await response.json();
const modal = document.getElementById('script-description-modal');
const scriptNameElement = document.getElementById('desc-modal-script-name');
const scriptFileElement = document.getElementById('desc-modal-script-file');
const contentElement = document.getElementById('script-description-content');
if (modal && scriptNameElement && contentElement) {
scriptNameElement.textContent = displayName;
scriptFileElement.textContent = `Archivo: ${scriptName}`;
// Renderizar markdown
if (metadata.long_description && metadata.long_description.trim()) {
const md = window.markdownit();
contentElement.innerHTML = md.render(metadata.long_description);
} else {
contentElement.innerHTML = '<p class="text-gray-500 italic">No hay descripción disponible para este script.</p>';
}
modal.classList.remove('hidden');
}
} catch (error) {
console.error('Error loading script description:', error);
alert('Error cargando la descripción del script');
}
}
// === GESTIÓN DE SCRIPTS INDIVIDUALES === // === GESTIÓN DE SCRIPTS INDIVIDUALES ===
async openScriptManager() { async openScriptManager() {
@ -334,7 +535,7 @@ class LauncherManager {
<p class="text-xs text-gray-500">Archivo: ${script.name}</p> <p class="text-xs text-gray-500">Archivo: ${script.name}</p>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<button onclick="launcherManager.editScriptMetadata('${script.name}')" <button onclick="launcherManager.editScriptMetadata('${script.name}')"
class="text-blue-500 hover:underline text-sm"> class="text-blue-500 hover:underline text-sm">
Editar Editar
</button> </button>
@ -525,11 +726,45 @@ class LauncherManager {
const timeAgo = this.getTimeAgo(entry.executed_date); const timeAgo = this.getTimeAgo(entry.executed_date);
const statusClass = entry.status === 'success' ? 'success' : const statusClass = entry.status === 'success' ? 'success' :
entry.status === 'error' ? 'error' : 'running'; entry.status === 'error' ? 'error' : 'running';
const statusIcon = entry.status === 'success' ? '✅' :
entry.status === 'error' ? '❌' : '🔄';
// Información del entorno Python // Iconos y mensajes más descriptivos por status
let statusIcon, statusText;
switch (entry.status) {
case 'success':
statusIcon = '✅';
statusText = 'Completado';
break;
case 'error':
statusIcon = '❌';
statusText = 'Error';
break;
case 'running':
statusIcon = '🔄';
statusText = 'En ejecución (GUI activa)';
break;
default:
statusIcon = '❓';
statusText = entry.status;
}
// Información del entorno Python y ejecutable
const envInfo = entry.python_env ? `${entry.python_env}` : ''; const envInfo = entry.python_env ? `${entry.python_env}` : '';
const executableType = entry.executable_type || 'python.exe';
const executableIcon = executableType.includes('pythonw') ? '🚀' : '🖥️';
// Botones para procesos en ejecución
const processButtons = entry.status === 'running' && entry.pid ? `
<div class="flex gap-1 mt-2">
<button onclick="launcherManager.focusProcess(${entry.pid})"
class="text-blue-500 hover:underline text-xs">
📍 Activar
</button>
<button onclick="launcherManager.terminateProcess(${entry.pid})"
class="text-red-500 hover:underline text-xs">
Cerrar
</button>
</div>
` : '';
const item = document.createElement('div'); const item = document.createElement('div');
item.className = `history-item ${statusClass}`; item.className = `history-item ${statusClass}`;
@ -542,10 +777,14 @@ class LauncherManager {
<span class="text-xs text-gray-400">${timeAgo}</span> <span class="text-xs text-gray-400">${timeAgo}</span>
</div> </div>
<div class="text-sm text-gray-600 mt-1"> <div class="text-sm text-gray-600 mt-1">
${statusIcon} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)} ${statusIcon} ${statusText}
${entry.execution_time ? ` - ${entry.execution_time}s` : ''} ${entry.execution_time ? ` ${entry.execution_time.toFixed(1)}s` : ''}
${entry.arguments && entry.arguments.length > 0 ? ` - Con argumentos` : ''} ${entry.arguments && entry.arguments.length > 0 ? ` Con argumentos` : ''}
</div> </div>
<div class="text-xs text-gray-500 mt-1">
${executableIcon} ${executableType}${entry.working_directory ? `${entry.working_directory}` : ''}
</div>
${processButtons}
`; `;
historyList.appendChild(item); historyList.appendChild(item);
}); });
@ -608,20 +847,46 @@ class LauncherManager {
} }
} }
async executeScript(scriptName, args = []) { showArgsModal(scriptName, displayName) {
const modal = document.getElementById('script-args-modal');
const scriptDisplayElement = document.getElementById('script-display-name');
const argsInput = document.getElementById('script-args-input');
const workingDirInput = document.getElementById('script-working-dir');
if (modal && scriptDisplayElement && argsInput) {
scriptDisplayElement.textContent = displayName;
argsInput.value = '';
workingDirInput.value = ''; // Limpiar directorio de trabajo
modal.classList.remove('hidden');
// Guardar datos para uso posterior
modal.dataset.scriptName = scriptName;
modal.dataset.groupId = this.currentGroup.id;
}
}
async executeScript(scriptName, args = [], workingDir = null, usePythonw = false) {
if (!this.currentGroup) return; if (!this.currentGroup) return;
try { try {
const requestData = {
group_id: this.currentGroup.id,
script_name: scriptName,
args: args,
use_pythonw: usePythonw
};
// Agregar directorio de trabajo si se especifica
if (workingDir && workingDir.trim()) {
requestData.working_dir = workingDir.trim();
}
const response = await fetch('/api/execute-gui-script', { const response = await fetch('/api/execute-gui-script', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify(requestData)
group_id: this.currentGroup.id,
script_name: scriptName,
args: args
})
}); });
const result = await response.json(); const result = await response.json();
@ -634,23 +899,21 @@ class LauncherManager {
} }
} }
showArgsModal(scriptName, displayName) { // Función para buscar directorio de trabajo
const modal = document.getElementById('script-args-modal'); browseWorkingDirectory() {
const scriptDisplayElement = document.getElementById('script-display-name'); fetch('/api/browse-directories')
const argsInput = document.getElementById('script-args-input'); .then(response => response.json())
.then(data => {
if (modal && scriptDisplayElement && argsInput) { if (data.status === 'success') {
scriptDisplayElement.textContent = displayName; document.getElementById('script-working-dir').value = data.path;
argsInput.value = ''; }
modal.classList.remove('hidden'); })
.catch(error => {
// Guardar datos para uso posterior console.error('Error browsing directory:', error);
modal.dataset.scriptName = scriptName; });
modal.dataset.groupId = this.currentGroup.id;
}
} }
renderFavorites(favorites) { async renderFavorites(favorites) {
const favoritesList = document.getElementById('favorites-list'); const favoritesList = document.getElementById('favorites-list');
const favoritesPanel = document.getElementById('favorites-panel'); const favoritesPanel = document.getElementById('favorites-panel');
@ -664,35 +927,154 @@ class LauncherManager {
favoritesPanel.classList.remove('empty'); favoritesPanel.classList.remove('empty');
favoritesList.innerHTML = ''; favoritesList.innerHTML = '';
favorites.slice(0, 5).forEach(fav => { // Cambiar a diseño de grid para cards más compactas
const group = this.groups.find(g => g.id === fav.group_id); favoritesList.className = 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3';
if (!group) return;
const item = document.createElement('div'); // Obtener metadatos de todos los scripts favoritos
item.className = 'flex items-center justify-between p-2 bg-white rounded border'; for (const fav of favorites.slice(0, 6)) { // Mostrar hasta 6 favoritos en grid
item.innerHTML = ` const group = this.groups.find(g => g.id === fav.group_id);
<div class="flex items-center"> if (!group) continue;
<div class="group-icon-small default mr-2">
${this.getDefaultIconForCategory(group.category)} try {
// Obtener metadatos del script para mostrar el display_name
const response = await fetch(`/api/launcher-script-metadata/${fav.group_id}/${fav.script_name}`);
const metadata = await response.json();
const displayName = metadata.display_name || fav.script_name.replace('.py', '');
const card = document.createElement('div');
card.className = 'bg-white border border-yellow-300 rounded-lg p-3 hover:shadow-md transition-all duration-200 hover:border-yellow-400';
// Crear contenedor para el icono que se actualizará dinámicamente
const iconId = `fav-icon-${fav.group_id}`;
card.innerHTML = `
<div class="flex items-start justify-between mb-2">
<div class="flex items-center min-w-0 flex-1">
<div id="${iconId}" class="group-icon-small default mr-2 flex-shrink-0">
${this.getDefaultIconForCategory(group.category)}
</div>
<div class="min-w-0 flex-1">
<div class="font-medium text-sm text-gray-900 truncate" title="${displayName}">
${displayName}
</div>
<div class="text-xs text-gray-500 truncate" title="${group.name}">
${group.name}
</div>
</div>
</div>
<button class="favorite-star active text-yellow-500 ml-2 flex-shrink-0"
onclick="launcherManager.toggleFavorite('${fav.group_id}', '${fav.script_name}')"
title="Quitar de favoritos">
</button>
</div> </div>
<div> <div class="flex gap-1">
<div class="font-medium text-sm">${fav.script_name.replace('.py', '')}</div> <button class="bg-blue-500 text-white px-2 py-1 rounded text-xs hover:bg-blue-600 flex-1"
<div class="text-xs text-gray-500">${group.name}</div> onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')"
title="Ejecutar script">
🖥 Ejecutar
</button>
<button class="bg-green-500 text-white px-2 py-1 rounded text-xs hover:bg-green-600 flex-1"
onclick="launcherManager.executeFavoriteScriptSilent('${fav.group_id}', '${fav.script_name}')"
title="Ejecutar sin ventana de consola">
🔇 Silencioso
</button>
</div> </div>
</div> `;
<button class="text-blue-500 hover:underline text-sm" favoritesList.appendChild(card);
onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')">
Ejecutar // Intentar cargar el icono personalizado del grupo después de agregar la card
</button> this.loadGroupIconForFavorite(iconId, fav.group_id, group.category);
`;
favoritesList.appendChild(item); } catch (error) {
}); console.error('Error loading script metadata for favorite:', error);
// Fallback al nombre del archivo
const card = document.createElement('div');
card.className = 'bg-white border border-yellow-300 rounded-lg p-3 hover:shadow-md transition-all duration-200 hover:border-yellow-400';
const iconId = `fav-icon-${fav.group_id}`;
card.innerHTML = `
<div class="flex items-start justify-between mb-2">
<div class="flex items-center min-w-0 flex-1">
<div id="${iconId}" class="group-icon-small default mr-2 flex-shrink-0">
${this.getDefaultIconForCategory(group.category)}
</div>
<div class="min-w-0 flex-1">
<div class="font-medium text-sm text-gray-900 truncate" title="${fav.script_name.replace('.py', '')}">
${fav.script_name.replace('.py', '')}
</div>
<div class="text-xs text-gray-500 truncate" title="${group.name}">
${group.name}
</div>
</div>
</div>
<button class="favorite-star active text-yellow-500 ml-2 flex-shrink-0"
onclick="launcherManager.toggleFavorite('${fav.group_id}', '${fav.script_name}')"
title="Quitar de favoritos">
</button>
</div>
<div class="flex gap-1">
<button class="bg-blue-500 text-white px-2 py-1 rounded text-xs hover:bg-blue-600 flex-1"
onclick="launcherManager.executeFavoriteScript('${fav.group_id}', '${fav.script_name}')"
title="Ejecutar script">
🖥 Ejecutar
</button>
<button class="bg-green-500 text-white px-2 py-1 rounded text-xs hover:bg-green-600 flex-1"
onclick="launcherManager.executeFavoriteScriptSilent('${fav.group_id}', '${fav.script_name}')"
title="Ejecutar sin ventana de consola">
🔇 Silencioso
</button>
</div>
`;
favoritesList.appendChild(card);
// Intentar cargar el icono personalizado del grupo
this.loadGroupIconForFavorite(iconId, fav.group_id, group.category);
}
}
}
// Nuevo método para cargar iconos específicos de grupo en favoritos
loadGroupIconForFavorite(iconElementId, groupId, fallbackCategory) {
const iconElement = document.getElementById(iconElementId);
if (!iconElement) return;
// Intentar cargar icono personalizado
const img = document.createElement('img');
img.src = `/api/group-icon/launcher/${groupId}`;
img.className = 'w-5 h-5 rounded object-cover';
img.onerror = () => {
// Fallback a icono por defecto basado en categoría
iconElement.innerHTML = this.getDefaultIconForCategory(fallbackCategory);
iconElement.className = 'group-icon-small default mr-2 flex-shrink-0';
};
img.onload = () => {
// Reemplazar con imagen personalizada
iconElement.innerHTML = '';
iconElement.className = 'mr-2 flex-shrink-0 flex items-center justify-center';
iconElement.appendChild(img);
};
}
// Nuevo método para ejecutar favoritos en modo silencioso
async executeFavoriteScriptSilent(groupId, scriptName) {
// Cambiar al grupo correcto si no está seleccionado
if (!this.currentGroup || this.currentGroup.id !== groupId) {
document.getElementById('launcher-group-select').value = groupId;
localStorage.setItem('launcher-selected-group', groupId);
await this.loadLauncherScripts();
}
this.executeScript(scriptName, [], null, true); // true para usar pythonw
} }
async executeFavoriteScript(groupId, scriptName) { async executeFavoriteScript(groupId, scriptName) {
// Cambiar al grupo correcto si no está seleccionado // Cambiar al grupo correcto si no está seleccionado
if (!this.currentGroup || this.currentGroup.id !== groupId) { if (!this.currentGroup || this.currentGroup.id !== groupId) {
document.getElementById('launcher-group-select').value = groupId; document.getElementById('launcher-group-select').value = groupId;
localStorage.setItem('launcher-selected-group', groupId);
await this.loadLauncherScripts(); await this.loadLauncherScripts();
} }
@ -788,6 +1170,7 @@ class LauncherManager {
this.scripts = []; this.scripts = [];
this.renderScripts(); this.renderScripts();
this.updateManageScriptsButton(false); this.updateManageScriptsButton(false);
this.updateEditorButtons(false); // Ocultar botones de editor
} }
} else { } else {
alert(`Error: ${result.message}`); alert(`Error: ${result.message}`);
@ -811,6 +1194,83 @@ class LauncherManager {
console.error('Error browsing directory:', error); console.error('Error browsing directory:', error);
}); });
} }
// Nueva función genérica para abrir en editor
async openGroupInEditor(editorCode, groupSystem, groupId) {
if (!groupId) {
alert('Selecciona un grupo primero');
return;
}
const editorName = editorCode.toUpperCase();
try {
const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, {
method: 'POST'
});
if (!response.ok) {
// If response is not OK, it might not be JSON (e.g., Flask error page)
const errorText = await response.text();
console.error(`Error al abrir ${editorName}: HTTP ${response.status}`, errorText);
alert(`Error al abrir ${editorName}: ${response.status} ${response.statusText}\n${errorText.substring(0, 200)}...`); // Show limited error text
} else {
const result = await response.json(); // Now it's safer to parse JSON
if (result.status === 'success') {
console.log(result.message);
// Opcional: mostrar un toast/notificación de éxito
// showToast(result.message, 'success');
} else {
console.error(`Error al abrir ${editorName}:`, result.message);
alert(`Error: ${result.message || 'Error desconocido.'}`);
}
}
} catch (error) {
console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error);
alert(`Error de red o del cliente al intentar abrir ${editorName}.`);
}
}
async focusProcess(pid) {
try {
const response = await fetch(`/api/launcher-process-focus/${pid}`, {
method: 'POST'
});
const result = await response.json();
if (result.status === 'success') {
console.log(result.message);
} else {
alert(`Error: ${result.message}`);
}
} catch (error) {
console.error('Error focusing process:', error);
alert('Error activando proceso');
}
}
async terminateProcess(pid) {
if (!confirm('¿Estás seguro de que quieres cerrar este proceso?')) return;
try {
const response = await fetch(`/api/launcher-process-terminate/${pid}`, {
method: 'POST'
});
const result = await response.json();
if (result.status === 'success') {
console.log(result.message);
// Recargar historial para actualizar estado
await this.loadHistory();
} else {
alert(`Error: ${result.message}`);
}
} catch (error) {
console.error('Error terminating process:', error);
alert('Error cerrando proceso');
}
}
} }
// === FUNCIONES GLOBALES === // === FUNCIONES GLOBALES ===
@ -909,17 +1369,50 @@ function closeArgsModal() {
function executeWithArgs() { function executeWithArgs() {
const modal = document.getElementById('script-args-modal'); const modal = document.getElementById('script-args-modal');
const argsInput = document.getElementById('script-args-input'); const argsInput = document.getElementById('script-args-input');
const workingDirInput = document.getElementById('script-working-dir');
const executionTypeInputs = document.getElementsByName('execution-type');
if (modal && argsInput && window.launcherManager) { if (modal && argsInput && window.launcherManager) {
const scriptName = modal.dataset.scriptName; const scriptName = modal.dataset.scriptName;
const args = argsInput.value.trim().split(/\s+/).filter(arg => arg.length > 0); const args = argsInput.value.trim().split(/\s+/).filter(arg => arg.length > 0);
const workingDir = workingDirInput.value.trim();
window.launcherManager.executeScript(scriptName, args); // Leer el tipo de ejecución seleccionado
let usePythonw = false;
for (const input of executionTypeInputs) {
if (input.checked) {
usePythonw = input.value === 'true';
break;
}
}
window.launcherManager.executeScript(scriptName, args, workingDir, usePythonw);
closeArgsModal(); closeArgsModal();
} }
} }
function browseWorkingDirectory() {
if (window.launcherManager) {
window.launcherManager.browseWorkingDirectory();
}
}
// Funciones para modal de descripción
function closeDescriptionModal() {
const modal = document.getElementById('script-description-modal');
if (modal) {
modal.classList.add('hidden');
}
}
function closeMarkdownViewer() {
const modal = document.getElementById('markdown-viewer-modal');
if (modal) {
modal.classList.add('hidden');
}
}
// Inicialización cuando se carga la página // Inicialización cuando se carga la página
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
console.log('Launcher JS loaded'); console.log('Launcher JS loaded');
}); });

View File

@ -1276,28 +1276,37 @@ async function saveConfig(level) {
} }
} }
async function openGroupInVsCode() { async function openGroupInEditor(editorCode, groupSystem, groupId) {
if (!currentGroup) { // groupId is already the currentGroup string from the select
if (!groupId) {
alert('Por favor, seleccione un grupo de scripts primero'); alert('Por favor, seleccione un grupo de scripts primero');
return; return;
} }
const editorName = editorCode.toUpperCase();
try { try {
const response = await fetch(`/api/open-vscode/${currentGroup}`, { const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, {
method: 'POST' method: 'POST'
}); });
const result = await response.json(); if (!response.ok) {
// If response is not OK, it might not be JSON (e.g., Flask error page)
if (result.status === 'success') { const errorText = await response.text();
console.log('VS Code opened successfully'); console.error(`Error al abrir ${editorName}: HTTP ${response.status}`, errorText);
alert(`Error al abrir ${editorName}: ${response.status} ${response.statusText}\n${errorText.substring(0, 200)}...`); // Show limited error text
} else { } else {
console.error('Error opening VS Code:', result.message); const result = await response.json(); // Now it's safer to parse JSON
alert(`Error al abrir VS Code: ${result.message}`); if (result.status === 'success') {
console.log(`${editorName} opened successfully`);
} else {
console.error(`Error al abrir ${editorName}:`, result.message);
alert(`Error al abrir ${editorName}: ${result.message}`);
}
} }
} catch (error) { } catch (error) {
console.error('Error calling open-vscode API:', error); console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error);
alert('Error al intentar abrir VS Code'); alert(`Error de red o del cliente al intentar abrir ${editorName}.`);
} }
} }

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Script Parameter Manager</title> <title>Script Parameter Manager</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}?v=1.1">
</head> </head>
<body class="bg-gray-100"> <body class="bg-gray-100">
@ -140,14 +140,21 @@
</path> </path>
</svg> </svg>
</button> </button>
<button onclick="openGroupInVsCode()" class="bg-blue-500 text-white p-2 rounded mb-2" <button onclick="openGroupInEditor('vscode', 'config', currentGroup)"
class="bg-blue-500 text-white p-2 rounded mb-2" id="vscode-config-btn"
title="Abrir grupo en VS Code"> title="Abrir grupo en VS Code">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <img src="{{ url_for('static', filename='icons/vscode.png') }}" class="w-5 h-5"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" alt="VS Code Icon">
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /> </button>
</svg> <!-- Nuevo botón para Cursor -->
<button onclick="openGroupInEditor('cursor', 'config', currentGroup)"
class="bg-blue-500 text-white p-2 rounded mb-2" id="cursor-config-btn"
title="Abrir grupo en Cursor">
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
alt="Cursor Icon">
</button> </button>
</div> </div>
<p id="group-description" class="text-gray-600 text-sm italic"></p> <p id="group-description" class="text-gray-600 text-sm italic"></p>
<div class="text-xs text-gray-500 mt-2"> <div class="text-xs text-gray-500 mt-2">
<span id="group-version"></span> <span id="group-version"></span>
@ -223,15 +230,34 @@
<!-- Group Selector --> <!-- Group Selector -->
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-medium mb-2">Seleccionar Grupo de Scripts</label> <label class="block text-sm font-medium mb-2">Seleccionar Grupo de Scripts</label>
<div class="relative"> <div class="flex gap-2">
<select id="launcher-group-select" class="w-full p-3 border rounded-lg pl-12" <div class="relative flex-1">
onchange="loadLauncherScripts()"> <select id="launcher-group-select" class="w-full p-3 border rounded-lg pl-12"
<option value="">-- Seleccionar Grupo --</option> onchange="loadLauncherScripts()">
</select> <option value="">-- Seleccionar Grupo --</option>
<div class="absolute left-3 top-1/2 transform -translate-y-1/2"> </select>
<div id="selected-group-icon" <div class="absolute left-3 top-1/2 transform -translate-y-1/2">
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁</div> <div id="selected-group-icon"
class="w-6 h-6 bg-gray-200 rounded flex items-center justify-center text-sm">📁
</div>
</div>
</div> </div>
<button
onclick="launcherManager.openGroupInEditor('vscode', 'launcher', launcherManager.currentGroup.id)"
class="bg-purple-500 text-white px-4 py-3 rounded-lg hover:bg-purple-600"
id="vscode-launcher-btn" style="display: none;" title="Abrir grupo en VS Code">
<img src="{{ url_for('static', filename='icons/vscode.png') }}" class="w-5 h-5"
alt="VS Code Icon">
</button>
<!-- Nuevo botón para Cursor -->
<button
onclick="launcherManager.openGroupInEditor('cursor', 'launcher', launcherManager.currentGroup.id)"
class="bg-purple-500 text-white px-4 py-3 rounded-lg hover:bg-purple-600"
id="cursor-launcher-btn" style="display: none;" title="Abrir grupo en Cursor">
<img src="{{ url_for('static', filename='icons/cursor.png') }}" class="w-5 h-5"
alt="Cursor Icon">
</button>
</div> </div>
</div> </div>
@ -289,6 +315,17 @@
</div> </div>
</div> </div>
<!-- Markdown Files Section -->
<div id="markdown-files-section" class="mb-6 bg-white p-6 rounded-lg shadow" style="display: none;">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold">📄 Documentación (Markdown)</h2>
<span class="text-sm text-gray-500">Archivos .md en el directorio del grupo</span>
</div>
<div id="markdown-files-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
<!-- Markdown files cards dinámicos -->
</div>
</div>
<!-- History Panel --> <!-- History Panel -->
<div class="mb-6 bg-white p-6 rounded-lg shadow"> <div class="mb-6 bg-white p-6 rounded-lg shadow">
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
@ -609,6 +646,39 @@
Separar argumentos con espacios. Usar comillas para valores con espacios. Separar argumentos con espacios. Usar comillas para valores con espacios.
</p> </p>
</div> </div>
<div>
<label class="block text-sm font-medium mb-1">
Directorio de Ejecución
</label>
<div class="flex gap-2">
<input type="text" id="script-working-dir" class="flex-1 p-2 border rounded"
placeholder="Por defecto: directorio del script">
<button type="button" onclick="browseWorkingDirectory()"
class="bg-gray-500 text-white px-3 py-2 rounded text-sm">
📁
</button>
</div>
</div>
<div>
<label class="block text-sm font-medium mb-1">
Tipo de Ejecución
</label>
<div class="flex gap-2">
<label class="flex items-center">
<input type="radio" name="execution-type" value="false" checked class="mr-2">
<span class="text-sm">🖥️ Con Log (python.exe)</span>
</label>
<label class="flex items-center">
<input type="radio" name="execution-type" value="true" class="mr-2">
<span class="text-sm">🚀 Sin Log (pythonw.exe)</span>
</label>
</div>
<p class="text-xs text-gray-500 mt-1">
Con Log: Muestra la salida del script. Sin Log: No muestra ventana de consola.
</p>
</div>
</div> </div>
<div class="flex justify-end gap-3 mt-6"> <div class="flex justify-end gap-3 mt-6">
@ -625,10 +695,71 @@
</div> </div>
</div> </div>
<!-- Script Description Modal (NUEVO) -->
<div id="script-description-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] overflow-hidden">
<div class="p-6 border-b">
<div class="flex justify-between items-start">
<div>
<h3 class="text-lg font-semibold" id="desc-modal-script-name">Descripción del Script</h3>
<p class="text-sm text-gray-600" id="desc-modal-script-file"></p>
</div>
<button onclick="closeDescriptionModal()"
class="text-gray-500 hover:text-gray-700 text-2xl">&times;</button>
</div>
</div>
<div class="p-6 overflow-y-auto max-h-[60vh]">
<div id="script-description-content" class="prose prose-sm max-w-none">
<!-- Contenido markdown renderizado -->
</div>
</div>
<div class="p-4 border-t bg-gray-50 flex justify-end">
<button onclick="closeDescriptionModal()"
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
Cerrar
</button>
</div>
</div>
</div>
</div>
<!-- Markdown Viewer Modal (NUEVO) -->
<div id="markdown-viewer-modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 z-50">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden">
<div class="p-6 border-b">
<div class="flex justify-between items-start">
<div>
<h3 class="text-lg font-semibold" id="markdown-viewer-title">Documento Markdown</h3>
<p class="text-sm text-gray-600" id="markdown-viewer-path"></p>
</div>
<button onclick="closeMarkdownViewer()"
class="text-gray-500 hover:text-gray-700 text-2xl">&times;</button>
</div>
</div>
<div class="p-6 overflow-y-auto max-h-[75vh]">
<div id="markdown-viewer-content" class="prose prose-lg max-w-none">
<!-- Contenido markdown renderizado -->
</div>
</div>
<div class="p-4 border-t bg-gray-50 flex justify-end">
<button onclick="closeMarkdownViewer()"
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
Cerrar
</button>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/markdown-it@14.1.0/dist/markdown-it.min.js"></script> <script src="https://unpkg.com/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
<script src="{{ url_for('static', filename='js/scripts.js') }}" defer></script> <script src="{{ url_for('static', filename='js/scripts.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/launcher.js') }}" defer></script> <script src="{{ url_for('static', filename='js/launcher.js') }}" defer></script>
<script> <script>
// Inicializar markdown-it globalmente
window.markdownit = window.markdownit || markdownit;
window.addEventListener('load', () => { window.addEventListener('load', () => {
console.log('Window loaded, initializing app...'); console.log('Window loaded, initializing app...');
if (typeof initializeApp === 'function') { if (typeof initializeApp === 'function') {