From 88806ee4e41dd7299d673324071ac7f3593b71db Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 6 Jun 2025 16:35:11 +0200 Subject: [PATCH] =?UTF-8?q?Implementaci=C3=B3n=20de=20nuevas=20funcionalid?= =?UTF-8?q?ades=20en=20el=20Launcher=20GUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- app.py | 222 +++++- .../IO_adaptation/log_x1_export_CAx.txt | 37 +- .../IO_adaptation/log_x2_process_CAx.txt | 83 ++- .../IO_adaptation/script_config.json | 2 +- .../script_groups/IO_adaptation/work_dir.json | 3 +- .../IO_adaptation/x1_export_CAx.py | 59 +- backend/script_groups/example_group/README.md | 33 + data/launcher_history.json | 178 ++++- data/launcher_script_metadata.json | 132 +++- data/launcher_scripts.json | 11 + data/log.txt | 88 ++- lib/launcher_manager.py | 496 ++++++++++++-- requirements.txt | 8 + run_utf8.bat | 12 + static/css/styles.css | 216 +++++- static/icons/cursor.png | Bin 0 -> 47702 bytes static/icons/vscode.png | Bin 0 -> 84020 bytes static/js/launcher.js | 633 ++++++++++++++++-- static/js/scripts.js | 31 +- templates/index.html | 159 ++++- 20 files changed, 2158 insertions(+), 245 deletions(-) create mode 100644 backend/script_groups/example_group/README.md create mode 100644 run_utf8.bat create mode 100644 static/icons/cursor.png create mode 100644 static/icons/vscode.png diff --git a/app.py b/app.py index 0078824..af6e936 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,7 @@ from datetime import datetime import time # Added for shutdown delay import sys # Added for platform detection import subprocess # Add this to the imports at the top +import shutil # For shutil.whichimport os # --- Imports for System Tray Icon --- import threading @@ -609,9 +610,11 @@ def execute_gui_script(): group_id = data["group_id"] script_name = data["script_name"] 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( - group_id, script_name, script_args, broadcast_message + group_id, script_name, script_args, broadcast_message, working_dir, use_pythonw ) return jsonify(result) except Exception as e: @@ -690,8 +693,225 @@ def get_group_icon(launcher_type, group_id): except Exception as e: return jsonify({"error": str(e)}), 500 +# Nuevas APIs para gestión de procesos y Markdown + +@app.route("/api/launcher-process-focus/", 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/", 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/", 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/") +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//") +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 "

Internal Server Error

An unhandled error occurred.

", 500 + + # === 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///", 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__": # --- Start Flask in a background thread --- diff --git a/backend/script_groups/IO_adaptation/log_x1_export_CAx.txt b/backend/script_groups/IO_adaptation/log_x1_export_CAx.txt index 8da41f3..06152c7 100644 --- a/backend/script_groups/IO_adaptation/log_x1_export_CAx.txt +++ b/backend/script_groups/IO_adaptation/log_x1_export_CAx.txt @@ -1,35 +1,36 @@ --- Log de Ejecución: x1_export_CAx.py --- Grupo: IO_adaptation -Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -Inicio: 2025-05-15 10:41:11 -Fin: 2025-05-15 10:43:18 -Duración: 0:02:07.367695 +Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Inicio: 2025-06-06 16:30:18 +Fin: 2025-06-06 16:33:55 +Duración: 0:03:37.052535 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- --- 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 -Using Output Directory (Working Directory): C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -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 -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 -Export log file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.log +Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19 +Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Detected TIA Portal version: 19.0 (from extension .ap19) +Will export CAx data to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +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... -2025-05-15 10:41:33,504 [1] INFO Siemens.TiaPortal.OpennessApi18.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 +Connecting to TIA Portal V19.0... +2025-06-06 16:30:25,861 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog. +2025-06-06 16:30:25,895 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface Connected. -Opening project: SAE196_c0.2.ap18... -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 +Opening project: 98050_PLC_01.ap19... +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. -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. 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. -Parsing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_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 +Parsing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +Markdown summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md Script finished. diff --git a/backend/script_groups/IO_adaptation/log_x2_process_CAx.txt b/backend/script_groups/IO_adaptation/log_x2_process_CAx.txt index a764a6f..ee7fb94 100644 --- a/backend/script_groups/IO_adaptation/log_x2_process_CAx.txt +++ b/backend/script_groups/IO_adaptation/log_x2_process_CAx.txt @@ -1,48 +1,67 @@ --- Log de Ejecución: x2_process_CAx.py --- Grupo: IO_adaptation -Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -Inicio: 2025-05-15 11:01:47 -Fin: 2025-05-15 11:01:52 -Duración: 0:00:04.917359 +Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Inicio: 2025-06-06 16:34:01 +Fin: 2025-06-06 16:34:09 +Duración: 0:00:08.316649 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- --- 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 -Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml -Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -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 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 -Processing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\SAE196_c0.2_CAx_Export.aml -Pass 1: Found 203 InternalElement(s). Populating device dictionary... +Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json +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: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml +Pass 1: Found 363 InternalElement(s). Populating device dictionary... Pass 2: Identifying PLCs and Networks (Refined v2)... - Identified Network: PROFIBUS_1 (442e4d1d-7d42-4d59-bd77-ec619f883907) Type: Profibus - Identified Network: ETHERNET_1 (26504433-7319-4b53-8f42-0ae24c9e88a2) Type: Ethernet/Profinet - Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0 + Identified Network: PN/IE_1 (fcbafb48-c53d-4af5-8143-794df99be4d6) Type: Unknown + Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0 Pass 3: Processing InternalLinks (Robust Network Mapping & IO)... -Found 115 InternalLink(s). - Mapping Device/Node 'E1' (NodeID:60d02ba8-54ea-4508-8a3a-986826e276c6, Addr:10.1.33.11) to Network 'ETHERNET_1' - --> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11) - Mapping Device/Node 'P1' (NodeID:8cf403ec-810d-43de-826a-aa447f887ee3, Addr:1) to Network 'PROFIBUS_1' - --> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1) - Mapping Device/Node 'PB1' (NodeID:b618b2b1-9ea8-41c1-a87e-fb0a3627a51d, Addr:12) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:4e5d84a4-eb22-4d1f-8143-fc4d770eb2e7, Addr:20) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:e6f362b4-398b-4854-b15b-7435745e4650, Addr:21) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:1a5422d6-c2bf-4a1c-8cf9-7b89fbaf4090, Addr:22) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:6b3de492-236f-4894-9bcf-3ee6c851c230, Addr:10) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:bf8c18a3-aa60-4106-b779-afff78cdac47, Addr:8) to Network 'PROFIBUS_1' - Mapping Device/Node 'PB1' (NodeID:5e2810b0-4018-4747-bba9-19b8f9b14994, Addr:40) to Network 'PROFIBUS_1' +Found 103 InternalLink(s). + Mapping Device/Node 'E1' (NodeID:ffef8b7b-b6bf-46aa-b3b5-f5083c420563, Addr:10.1.30.11) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:221c248d-83d8-464c-9425-c949086b34e7, Addr:10.1.30.58) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:5c3bf795-ecec-47e7-a962-4d54c95b9887, Addr:10.1.30.59) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:4100c829-d889-489a-971c-a69207333e2f, Addr:10.1.30.60) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:a6aed467-1a4c-4a5a-bf3d-e7ce46e94a9f, Addr:10.1.30.61) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:b3d71b04-f99a-4af8-aa69-2ca02a30d391, Addr:10.1.30.62) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:eb59daf8-f59c-435e-bfe4-8e7afd0937bd, Addr:10.1.30.63) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:3e931f82-08a5-44de-89a5-b907f5aaeb24, Addr:10.1.30.64) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:f673d611-7a73-4a8e-912d-ea16c294d51b, Addr:10.1.30.65) to Network 'PN/IE_1' + Mapping Device/Node 'IE1' (NodeID:09a2569a-4948-4830-9dd8-315ae638e391, Addr:10.1.30.66) to Network 'PN/IE_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. -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. -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... - 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 -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 -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 + 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: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md Script finished. diff --git a/backend/script_groups/IO_adaptation/script_config.json b/backend/script_groups/IO_adaptation/script_config.json index 5b48ed3..51c7241 100644 --- a/backend/script_groups/IO_adaptation/script_config.json +++ b/backend/script_groups/IO_adaptation/script_config.json @@ -8,5 +8,5 @@ "ObsideanProjectsBase": "\\04-SIDEL" }, "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" } \ No newline at end of file diff --git a/backend/script_groups/IO_adaptation/work_dir.json b/backend/script_groups/IO_adaptation/work_dir.json index 9e3eade..d181f0b 100644 --- a/backend/script_groups/IO_adaptation/work_dir.json +++ b/backend/script_groups/IO_adaptation/work_dir.json @@ -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": [ + "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia", "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2" ] } \ No newline at end of file diff --git a/backend/script_groups/IO_adaptation/x1_export_CAx.py b/backend/script_groups/IO_adaptation/x1_export_CAx.py index d9fbcc7..9b803d2 100644 --- a/backend/script_groups/IO_adaptation/x1_export_CAx.py +++ b/backend/script_groups/IO_adaptation/x1_export_CAx.py @@ -17,7 +17,12 @@ sys.path.append(script_root) from backend.script_utils import load_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 --- # (Same import handling as the previous script) @@ -40,18 +45,43 @@ except Exception as e: # --- 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(): """Opens a dialog to select a TIA Portal project file.""" root = tk.Tk() root.withdraw() file_path = filedialog.askopenfilename( title="Select TIA Portal Project File", - filetypes=[ - ( - f"TIA Portal V{TIA_PORTAL_VERSION} Projects", - f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}", - ) - ], + filetypes=get_supported_filetypes(), ) root.destroy() if not file_path: @@ -242,6 +272,9 @@ if __name__ == "__main__": print(f"\nSelected Project: {project_file}") 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 project_path = Path(project_file) project_base_name = project_path.stem # Get filename without extension @@ -260,15 +293,15 @@ if __name__ == "__main__": cax_export_successful = False try: - # 2. Connect to TIA Portal - print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...") + # 3. Connect to TIA Portal with detected version + print(f"\nConnecting to TIA Portal V{tia_version}...") portal_instance = ts.open_portal( - version=TIA_PORTAL_VERSION, + version=tia_version, portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface, ) print("Connected.") - # 3. Open Project + # 4. Open Project print( f"Opening project: {project_path.name}..." ) # Use Path object's name attribute @@ -282,7 +315,7 @@ if __name__ == "__main__": raise Exception("Failed to open or get the specified project.") 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}...") # Ensure output directory exists (Path.mkdir handles this implicitly if needed later, but good practice) output_dir.mkdir(parents=True, exist_ok=True) @@ -322,7 +355,7 @@ if __name__ == "__main__": except Exception as 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 aml_file.exists(): # Use Path object's exists() method parse_aml_to_markdown(aml_file, md_file) diff --git a/backend/script_groups/example_group/README.md b/backend/script_groups/example_group/README.md new file mode 100644 index 0000000..c563ef2 --- /dev/null +++ b/backend/script_groups/example_group/README.md @@ -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 \ No newline at end of file diff --git a/data/launcher_history.json b/data/launcher_history.json index 057c798..1d5a6f2 100644 --- a/data/launcher_history.json +++ b/data/launcher_history.json @@ -1,47 +1,199 @@ { "history": [ { - "id": "03b026d2", + "id": "6d4e8908", "group_id": "1", "script_name": "calc.py", - "executed_date": "2025-06-03T12:09:48.856960Z", + "executed_date": "2025-06-05T19:00:05.863426Z", "arguments": [], "working_directory": "D:/Proyectos/Scripts/Calcv2", "python_env": "tia_scripting", + "executable_type": "pythonw.exe", "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", "script_name": "main.py", - "executed_date": "2025-06-03T12:09:12.887585Z", + "executed_date": "2025-06-04T22:07:51.493125Z", "arguments": [], "working_directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp", "python_env": "tia_scripting", - "status": "running", - "pid": 18248 + "executable_type": "python.exe", + "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", "script_name": "calc.py", - "executed_date": "2025-06-03T12:08:27.391884Z", + "executed_date": "2025-06-04T18:56:27.720254Z", "arguments": [], "working_directory": "D:/Proyectos/Scripts/Calcv2", "python_env": "tia_scripting", + "executable_type": "pythonw.exe", "status": "running", - "pid": 38664 + "pid": 43496, + "execution_time": null }, { - "id": "f0b71d84", + "id": "b7796abb", "group_id": "1", "script_name": "calc.py", - "executed_date": "2025-06-03T11:48:50.783603Z", + "executed_date": "2025-06-04T18:56:17.950282Z", "arguments": [], "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", - "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": { diff --git a/data/launcher_script_metadata.json b/data/launcher_script_metadata.json index 1a7c8f5..48ecb73 100644 --- a/data/launcher_script_metadata.json +++ b/data/launcher_script_metadata.json @@ -47,8 +47,8 @@ "display_name": "test_symbolic", "description": "", "long_description": "", - "hidden": true, - "updated_date": "2025-06-03T12:06:00.100424Z" + "hidden": false, + "updated_date": "2025-06-03T13:44:26.045048Z" }, "1_tl_bracket_parser.py": { "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", "hidden": false, "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" } \ No newline at end of file diff --git a/data/launcher_scripts.json b/data/launcher_scripts.json index 69b1964..bd0c9fe 100644 --- a/data/launcher_scripts.json +++ b/data/launcher_scripts.json @@ -22,6 +22,17 @@ "directory": "D:/Proyectos/Scripts/RS485/MaselliSimulatorApp", "created_date": "2025-06-03T11:57:31.622922Z", "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": { diff --git a/data/log.txt b/data/log.txt index 35d4168..6d68b34 100644 --- a/data/log.txt +++ b/data/log.txt @@ -1,6 +1,82 @@ -[12:09:48] Ejecutando script GUI: calc.py -[12:09:48] Entorno Python: tia_scripting -[12:09:48] Comando: C:\Users\migue\miniconda3\envs\tia_scripting\python.exe D:/Proyectos/Scripts/Calcv2\calc.py -[12:09:48] Directorio: D:/Proyectos/Scripts/Calcv2 -[12:09:48] Script GUI ejecutado con PID: 47056 -[12:09:48] ID de ejecución: 03b026d2 +[16:30:18] Iniciando ejecución de x1_export_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia... +[16:30:19] --- TIA Portal Project CAx Exporter and Analyzer --- +[16:30:24] Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19 +[16:30:24] Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia +[16:30:24] Detected TIA Portal version: 19.0 (from extension .ap19) +[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 diff --git a/lib/launcher_manager.py b/lib/launcher_manager.py index a9317ab..1252152 100644 --- a/lib/launcher_manager.py +++ b/lib/launcher_manager.py @@ -2,6 +2,8 @@ import os import json import subprocess import sys +import threading +import time from typing import Dict, Any, List, Optional from datetime import datetime import uuid @@ -219,45 +221,57 @@ class LauncherManager: def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: """Obtener scripts de un grupo específico con metadatos""" try: + print(f"[DEBUG] Loading scripts for group: {group_id}") group = self.get_launcher_group(group_id) if not group: + print(f"[DEBUG] Group {group_id} not found") return [] directory = group["directory"] + print(f"[DEBUG] Group directory: {directory}") if not os.path.isdir(directory): + print(f"[DEBUG] Directory {directory} does not exist or is not a directory") return [] # Cargar metadatos de scripts script_metadata = self._load_script_metadata() scripts = [] - for file in os.listdir(directory): - if file.endswith('.py') and not file.startswith('_'): - 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) - }) + python_files = [f for f in os.listdir(directory) if f.endswith('.py') and not f.startswith('_')] + print(f"[DEBUG] Found {len(python_files)} Python files: {python_files}") + 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"]) except Exception as e: print(f"Error getting scripts for group {group_id}: {e}") + import traceback + traceback.print_exc() return [] 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}] def execute_gui_script(self, group_id: str, script_name: str, script_args: List[str], - broadcast_func) -> Dict[str, Any]: - """Ejecutar script GUI con argumentos opcionales y entorno específico""" + broadcast_func, working_dir: str = None, use_pythonw: bool = False) -> Dict[str, Any]: + """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: group = self.get_launcher_group(group_id) if not group: @@ -387,27 +405,149 @@ class LauncherManager: # Determinar el ejecutable de Python a usar 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 - cmd = [python_executable, script_path] + script_args - working_dir = group["directory"] # Por defecto directorio del script + # Determinar directorio de trabajo + if working_dir and os.path.isdir(working_dir): + 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"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"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 start_time = datetime.now() - process = subprocess.Popen( - cmd, - cwd=working_dir, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - creationflags=subprocess.CREATE_NEW_CONSOLE if sys.platform == "win32" else 0 - ) + + if use_pythonw: + # Con pythonw.exe: No capturar salida, solo ejecutar + try: + process = subprocess.Popen( + cmd, + 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 execution_id = str(uuid.uuid4())[:8] @@ -417,13 +557,14 @@ class LauncherManager: "script_name": script_name, "executed_date": start_time.isoformat() + "Z", "arguments": script_args, - "working_directory": working_dir, + "working_directory": exec_working_dir, "python_env": python_env, - "status": "running", - "pid": process.pid + "executable_type": "pythonw.exe" if use_pythonw else "python.exe", + "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}") return { @@ -438,9 +579,23 @@ class LauncherManager: broadcast_func(error_msg) return {"status": "error", "message": error_msg} - def _get_python_executable(self, env_name: str) -> str: - """Obtener el ejecutable de Python para un entorno específico""" + def _get_python_executable(self, env_name: str, use_pythonw: bool = False) -> str: + """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": + # 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 # Buscar en entornos de Miniconda @@ -453,11 +608,23 @@ class LauncherManager: ] for base_path in miniconda_paths: - env_path = os.path.join(base_path, "envs", env_name, "python.exe") - if os.path.exists(env_path): - return env_path + if use_pythonw: + # Intentar pythonw.exe para GUI sin consola + 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 def _load_script_metadata(self) -> Dict[str, Any]: @@ -615,4 +782,237 @@ class LauncherManager: json.dump(data, f, indent=2, ensure_ascii=False) except Exception as e: - print(f"Error cleaning up favorites for group {group_id}: {e}") \ No newline at end of file + 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)}"} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bc73f10..530dc19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,11 @@ pypandoc==1.15 Requests==2.32.3 siemens_tia_scripting==1.0.7 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 diff --git a/run_utf8.bat b/run_utf8.bat new file mode 100644 index 0000000..e48ec33 --- /dev/null +++ b/run_utf8.bat @@ -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 \ No newline at end of file diff --git a/static/css/styles.css b/static/css/styles.css index a17a408..447ef9e 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -221,8 +221,8 @@ } .group-icon-small { - width: 24px; - height: 24px; + width: 20px; + height: 20px; border-radius: 4px; object-fit: cover; border: 1px solid #E5E7EB; @@ -234,7 +234,7 @@ align-items: center; justify-content: center; color: white; - font-size: 12px; + font-size: 10px; } /* Category Styles */ @@ -291,12 +291,10 @@ .favorite-star { cursor: pointer; transition: all 0.2s ease; - color: #D1D5DB; } .favorite-star:hover { transform: scale(1.1); - color: #F59E0B; } .favorite-star.active { @@ -305,16 +303,11 @@ /* History Item */ .history-item { + padding: 1rem; + margin-bottom: 0.5rem; + border-radius: 0.5rem; border: 1px solid #E5E7EB; - border-radius: 6px; - padding: 0.75rem; - background: #F9FAFB; - transition: all 0.2s ease; -} - -.history-item:hover { - background: #F3F4F6; - border-color: #D1D5DB; + background: white; } .history-item.success { @@ -327,6 +320,19 @@ .history-item.running { 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 */ @@ -338,6 +344,19 @@ 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 { display: flex; @@ -533,4 +552,173 @@ .focus-visible:focus { outline: 2px solid #3B82F6; 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; } \ No newline at end of file diff --git a/static/icons/cursor.png b/static/icons/cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..85ae6f0ad6c45e96e4e07ee4367b28bb0b03904a GIT binary patch literal 47702 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⩔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 literal 0 HcmV?d00001 diff --git a/static/icons/vscode.png b/static/icons/vscode.png new file mode 100644 index 0000000000000000000000000000000000000000..f147745075bcf0351fb99f5cc87ae870cfb33ad7 GIT binary patch literal 84020 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

s30Cq>AiO0!twZ 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 `${f.group_id}_${f.script_name}`)); - this.renderFavorites(data.favorites); + await this.renderFavorites(data.favorites); } catch (error) { console.error('Error loading favorites:', error); } @@ -118,9 +119,15 @@ class LauncherManager { const selector = document.getElementById('launcher-group-select'); if (!selector) return; + // Guardar la selección actual + const currentSelection = this.getCurrentGroupSelection(); + selector.innerHTML = ''; - 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'); option.value = group.id; option.textContent = group.name; @@ -128,6 +135,32 @@ class LauncherManager { option.dataset.description = group.description; 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() { @@ -152,26 +185,60 @@ class LauncherManager { async loadLauncherScripts() { 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) { this.scripts = []; + this.markdownFiles = []; this.renderScripts(); + this.renderMarkdownFiles(); this.updateManageScriptsButton(false); + this.updateEditorButtons(false); return; } + // Cargar scripts (parte crítica) try { const response = await fetch(`/api/launcher-scripts/${groupId}`); - this.scripts = await response.json(); - this.currentGroup = this.groups.find(g => g.id === groupId); - this.updateGroupIcon(); - this.renderScripts(); - this.updateManageScriptsButton(true); + if (response.ok) { + this.scripts = await response.json(); + } else { + console.error('Error loading scripts:', response.status, response.statusText); + this.scripts = []; + } } catch (error) { console.error('Error loading launcher scripts:', error); 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) { @@ -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() { const iconElement = document.getElementById('selected-group-icon'); if (!iconElement || !this.currentGroup) return; @@ -230,7 +309,9 @@ class LauncherManager { let filteredScripts = this.scripts; 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 = []; } } @@ -245,7 +326,7 @@ class LauncherManager { card.innerHTML = `

${script.display_name}

- @@ -253,15 +334,30 @@ class LauncherManager {

${script.description || 'Script: ' + script.name}

${this.currentGroup.category} -
- - +
+
+ ${script.long_description && script.long_description.trim() ? ` + ` : ''} + +
+
+ + +
`; @@ -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 = ` +
+
+

${file.display_name}

+

+ 📄 ${(file.size / 1024).toFixed(1)} KB + ${file.level === 1 ? ' • Subdirectorio' : ''} +

+
+ ${this.getTimeAgo(file.modified)} +
+ `; + 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 = '

No hay descripción disponible para este script.

'; + } + + 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 === async openScriptManager() { @@ -334,7 +535,7 @@ class LauncherManager {

Archivo: ${script.name}

- @@ -525,11 +726,45 @@ class LauncherManager { const timeAgo = this.getTimeAgo(entry.executed_date); const statusClass = entry.status === 'success' ? 'success' : 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 executableType = entry.executable_type || 'python.exe'; + const executableIcon = executableType.includes('pythonw') ? '🚀' : '🖥️'; + + // Botones para procesos en ejecución + const processButtons = entry.status === 'running' && entry.pid ? ` +
+ + +
+ ` : ''; const item = document.createElement('div'); item.className = `history-item ${statusClass}`; @@ -542,10 +777,14 @@ class LauncherManager { ${timeAgo}
- ${statusIcon} ${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)} - ${entry.execution_time ? ` - ${entry.execution_time}s` : ''} - ${entry.arguments && entry.arguments.length > 0 ? ` - Con argumentos` : ''} + ${statusIcon} ${statusText} + ${entry.execution_time ? ` • ${entry.execution_time.toFixed(1)}s` : ''} + ${entry.arguments && entry.arguments.length > 0 ? ` • Con argumentos` : ''}
+
+ ${executableIcon} ${executableType}${entry.working_directory ? ` • ${entry.working_directory}` : ''} +
+ ${processButtons} `; 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; 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', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ - group_id: this.currentGroup.id, - script_name: scriptName, - args: args - }) + body: JSON.stringify(requestData) }); const result = await response.json(); @@ -634,23 +899,21 @@ class LauncherManager { } } - showArgsModal(scriptName, displayName) { - const modal = document.getElementById('script-args-modal'); - const scriptDisplayElement = document.getElementById('script-display-name'); - const argsInput = document.getElementById('script-args-input'); - - if (modal && scriptDisplayElement && argsInput) { - scriptDisplayElement.textContent = displayName; - argsInput.value = ''; - modal.classList.remove('hidden'); - - // Guardar datos para uso posterior - modal.dataset.scriptName = scriptName; - modal.dataset.groupId = this.currentGroup.id; - } + // Función para buscar directorio de trabajo + browseWorkingDirectory() { + fetch('/api/browse-directories') + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + document.getElementById('script-working-dir').value = data.path; + } + }) + .catch(error => { + console.error('Error browsing directory:', error); + }); } - renderFavorites(favorites) { + async renderFavorites(favorites) { const favoritesList = document.getElementById('favorites-list'); const favoritesPanel = document.getElementById('favorites-panel'); @@ -664,35 +927,154 @@ class LauncherManager { favoritesPanel.classList.remove('empty'); favoritesList.innerHTML = ''; - favorites.slice(0, 5).forEach(fav => { - const group = this.groups.find(g => g.id === fav.group_id); - if (!group) return; + // Cambiar a diseño de grid para cards más compactas + favoritesList.className = 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3'; - const item = document.createElement('div'); - item.className = 'flex items-center justify-between p-2 bg-white rounded border'; - item.innerHTML = ` -
-
- ${this.getDefaultIconForCategory(group.category)} + // Obtener metadatos de todos los scripts favoritos + for (const fav of favorites.slice(0, 6)) { // Mostrar hasta 6 favoritos en grid + const group = this.groups.find(g => g.id === fav.group_id); + if (!group) continue; + + 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 = ` +
+
+
+ ${this.getDefaultIconForCategory(group.category)} +
+
+
+ ${displayName} +
+
+ ${group.name} +
+
+
+
-
-
${fav.script_name.replace('.py', '')}
-
${group.name}
+
+ +
-
- - `; - favoritesList.appendChild(item); - }); + `; + favoritesList.appendChild(card); + + // Intentar cargar el icono personalizado del grupo después de agregar la card + this.loadGroupIconForFavorite(iconId, fav.group_id, group.category); + + } 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 = ` +
+
+
+ ${this.getDefaultIconForCategory(group.category)} +
+
+
+ ${fav.script_name.replace('.py', '')} +
+
+ ${group.name} +
+
+
+ +
+
+ + +
+ `; + 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) { // 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(); } @@ -788,6 +1170,7 @@ class LauncherManager { this.scripts = []; this.renderScripts(); this.updateManageScriptsButton(false); + this.updateEditorButtons(false); // Ocultar botones de editor } } else { alert(`Error: ${result.message}`); @@ -811,6 +1194,83 @@ class LauncherManager { 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 === @@ -909,17 +1369,50 @@ function closeArgsModal() { function executeWithArgs() { const modal = document.getElementById('script-args-modal'); 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) { const scriptName = modal.dataset.scriptName; 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(); } } +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 document.addEventListener('DOMContentLoaded', function () { console.log('Launcher JS loaded'); -}); \ No newline at end of file +}); diff --git a/static/js/scripts.js b/static/js/scripts.js index 79fcd23..9e1c51a 100644 --- a/static/js/scripts.js +++ b/static/js/scripts.js @@ -1276,28 +1276,37 @@ async function saveConfig(level) { } } -async function openGroupInVsCode() { - if (!currentGroup) { +async function openGroupInEditor(editorCode, groupSystem, groupId) { + // groupId is already the currentGroup string from the select + if (!groupId) { alert('Por favor, seleccione un grupo de scripts primero'); return; } + const editorName = editorCode.toUpperCase(); + try { - const response = await fetch(`/api/open-vscode/${currentGroup}`, { + const response = await fetch(`/api/open-editor/${editorCode}/${groupSystem}/${groupId}`, { method: 'POST' }); - const result = await response.json(); - - if (result.status === 'success') { - console.log('VS Code opened successfully'); + 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 { - console.error('Error opening VS Code:', result.message); - alert(`Error al abrir VS Code: ${result.message}`); + const result = await response.json(); // Now it's safer to parse JSON + 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) { - console.error('Error calling open-vscode API:', error); - alert('Error al intentar abrir VS Code'); + console.error(`Error en la llamada fetch para open-editor (${editorName}):`, error); + alert(`Error de red o del cliente al intentar abrir ${editorName}.`); } } diff --git a/templates/index.html b/templates/index.html index 77c8cb8..61e3f31 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,7 +6,7 @@ Script Parameter Manager - + @@ -140,14 +140,21 @@ - + +
+

@@ -223,15 +230,34 @@
-
- -
-
📁
+
+
+ +
+
📁 +
+
+ + + +
@@ -289,6 +315,17 @@
+ + +
@@ -609,6 +646,39 @@ Separar argumentos con espacios. Usar comillas para valores con espacios.

+ +
+ +
+ + +
+
+ +
+ +
+ + +
+

+ Con Log: Muestra la salida del script. Sin Log: No muestra ventana de consola. +

+
@@ -625,10 +695,71 @@
+ + + + + +