From 7f5d7ff0331380809a72b45bb7f9ed41156233a2 Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 15 May 2025 14:11:24 +0200 Subject: [PATCH] Varias mejoras en el grupo de IO Adapted. Creado boton para abrir vscode y miniconda para instalar nuevas librerias --- app.py | 197 +++++++++++++-- backend/script_groups/IO_adaptation/data.json | 4 +- .../IO_adaptation/esquema_group.json | 17 +- .../IO_adaptation/log_x0_documentation.txt | 14 + .../IO_adaptation/log_x4_prompt_generator.txt | 23 +- .../IO_adaptation/script_config.json | 4 +- .../IO_adaptation/x4_prompt_generator.py | 239 ++++++++++++++---- data/log.txt | 29 +-- static/js/scripts.js | 48 +++- templates/index.html | 8 + 10 files changed, 463 insertions(+), 120 deletions(-) create mode 100644 backend/script_groups/IO_adaptation/log_x0_documentation.txt diff --git a/app.py b/app.py index 32700f1..bd8a5ea 100644 --- a/app.py +++ b/app.py @@ -4,13 +4,14 @@ from lib.config_manager import ConfigurationManager import os import json # Added import from datetime import datetime -import time # Added for shutdown delay +import time # Added for shutdown delay +import subprocess # Add this to the imports at the top # --- Imports for System Tray Icon --- import threading import webbrowser import sys -import requests # To send shutdown request +import requests # To send shutdown request from PIL import Image import pystray @@ -60,7 +61,7 @@ def broadcast_message(message): try: closing_bracket = raw_msg.index("]") + 1 if raw_msg[1 : closing_bracket - 1].replace(":", "").isdigit(): - raw_msg = raw_msg[closing_bracket:].strip() # Update raw_msg itself + raw_msg = raw_msg[closing_bracket:].strip() # Update raw_msg itself else: break except: @@ -76,10 +77,12 @@ def broadcast_message(message): # Enviar a todos los clientes WebSocket for ws in list(websocket_connections): try: - if ws.connected: # Check if ws is still connected before sending - ws.send(f"{formatted_msg_for_ws}\n") # Use the correct variable name here + if ws.connected: # Check if ws is still connected before sending + ws.send( + f"{formatted_msg_for_ws}\n" + ) # Use the correct variable name here except Exception: - dead_connections.add(ws) # Collect dead connections + dead_connections.add(ws) # Collect dead connections # Limpiar conexiones muertas websocket_connections.difference_update(dead_connections) @@ -139,7 +142,17 @@ def get_scripts(group): # list_scripts ahora devuelve detalles y filtra los ocultos scripts = config_manager.list_scripts(group) # El frontend espera 'name' y 'description', mapeamos desde 'display_name' y 'short_description' - return jsonify([{"name": s['display_name'], "description": s['short_description'], "filename": s['filename'], "long_description": s['long_description']} for s in scripts]) + return jsonify( + [ + { + "name": s["display_name"], + "description": s["short_description"], + "filename": s["filename"], + "long_description": s["long_description"], + } + for s in scripts + ] + ) @app.route("/api/working-directory", methods=["POST"]) @@ -212,7 +225,7 @@ def handle_group_description(group): try: details = config_manager.get_group_details(group) if "error" in details: - return jsonify(details), 404 # Group not found + return jsonify(details), 404 # Group not found return jsonify(details) except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 @@ -234,7 +247,7 @@ def handle_script_details(group, script_filename): except Exception as e: print(f"Error getting script details for {group}/{script_filename}: {e}") return jsonify({"status": "error", "message": str(e)}), 500 - else: # POST + else: # POST try: data = request.json result = config_manager.update_script_details(group, script_filename, data) @@ -250,15 +263,144 @@ def get_directory_history(group): return jsonify(history) +@app.route("/api/open-vscode/", methods=["POST"]) +def open_group_in_vscode(group): + try: + # Get the full path to the script group directory + script_group_path = os.path.join(config_manager.script_groups_path, group) + + if not os.path.isdir(script_group_path): + return ( + jsonify( + { + "status": "error", + "message": f"Directorio del grupo '{group}' no encontrado", + } + ), + 404, + ) + + # VS Code executable path + vscode_path = ( + r"C:\Users\migue\AppData\Local\Programs\Microsoft VS Code\Code.exe" + ) + + # Check if the file exists + 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 from: {vscode_path}") + print(f"Opening directory: {script_group_path}") + + # Try with shell=True which can help with Windows path issues + process = subprocess.Popen(f'"{vscode_path}" "{script_group_path}"', shell=True) + + # Log the process ID for debugging + 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 group '{group}': {str(e)}") + return ( + jsonify( + {"status": "error", "message": f"Error al abrir VS Code: {str(e)}"} + ), + 500, + ) + + +@app.route("/api/open-miniconda", methods=["POST"]) +def open_miniconda_console(): + try: + # Path to the Miniconda installation + miniconda_path = r"C:\Users\migue\miniconda3" + + # Check if directory exists + if not os.path.isdir(miniconda_path): + return ( + jsonify( + { + "status": "error", + "message": f"Miniconda no encontrado en: {miniconda_path}", + } + ), + 404, + ) + + # Path to the activate script + activate_path = os.path.join(miniconda_path, "Scripts", "activate.bat") + + if not os.path.isfile(activate_path): + return ( + jsonify( + { + "status": "error", + "message": f"Script de activación no encontrado en: {activate_path}", + } + ), + 404, + ) + + print(f"Opening Miniconda Console from: {miniconda_path}") + + # Use subprocess with CREATE_NEW_CONSOLE flag to ensure the window appears + # Start the Windows command processor and tell it to run the activate batch file + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = 1 # SW_SHOWNORMAL + + # Run cmd.exe with specific flags to show a window + process = subprocess.Popen( + [ + "cmd.exe", + "/K", + f"echo Activating Miniconda environment... && " + f'"{activate_path}" && ' + f"echo Miniconda activated successfully. && " + f"cd /d {miniconda_path}", + ], + creationflags=subprocess.CREATE_NEW_CONSOLE, + startupinfo=startupinfo, + ) + + print(f"Miniconda Console process started with PID: {process.pid}") + + return jsonify( + {"status": "success", "message": "Miniconda Console abierta correctamente"} + ) + except Exception as e: + print(f"Error opening Miniconda Console: {str(e)}") + return ( + jsonify( + { + "status": "error", + "message": f"Error al abrir Miniconda Console: {str(e)}", + } + ), + 500, + ) + + # --- System Tray Icon Functions --- + def run_flask(): """Runs the Flask app.""" print("Starting Flask server on http://127.0.0.1:5000/") try: # use_reloader=False is important when running in a thread # For production, consider using waitress or gunicorn instead of app.run - app.run(host='127.0.0.1', port=5000, debug=True, use_reloader=False) + app.run(host="127.0.0.1", port=5000, debug=True, use_reloader=False) except Exception as e: print(f"Error running Flask app: {e}") # Optionally try to stop the tray icon if Flask fails critically @@ -266,11 +408,13 @@ def run_flask(): print("Attempting to stop tray icon due to Flask error.") tray_icon.stop() + def open_app_browser(icon, item): """Callback function to open the browser.""" print("Opening application in browser...") webbrowser.open("http://127.0.0.1:5000/") + def shutdown_flask_server(): """Attempts to gracefully shut down the Werkzeug server.""" try: @@ -281,9 +425,10 @@ def shutdown_flask_server(): print(f"Could not send shutdown request to Flask server: {e}") print("Flask server might need to be closed manually.") + def stop_icon_thread(): """Helper function to stop the icon after a delay, allowing HTTP response.""" - time.sleep(0.1) # Small delay to allow the HTTP response to be sent + time.sleep(0.1) # Small delay to allow the HTTP response to be sent if tray_icon: print("Stopping tray icon from shutdown route...") tray_icon.stop() @@ -293,7 +438,8 @@ def stop_icon_thread(): # print("Attempting os._exit(0) as fallback.") # os._exit(0) # Force exit - use with caution -@app.route('/_shutdown', methods=['POST']) + +@app.route("/_shutdown", methods=["POST"]) def shutdown_route(): """Internal route to shut down the application via the tray icon.""" print("Shutdown endpoint called.") @@ -304,43 +450,52 @@ def shutdown_route(): print("Shutdown signal sent to tray icon thread.") return jsonify(status="success", message="Application shutdown initiated..."), 200 + def exit_application(icon, item): """Callback function to exit the application.""" print("Exit requested via tray menu.") # Just stop the icon. This will end the main thread, and the daemon Flask thread will exit. print("Stopping tray icon...") - if icon: # pystray passes the icon object + if icon: # pystray passes the icon object icon.stop() - elif tray_icon: # Fallback just in case + elif tray_icon: # Fallback just in case tray_icon.stop() + if __name__ == "__main__": # --- Start Flask in a background thread --- flask_thread = threading.Thread(target=run_flask, daemon=True) flask_thread.start() # --- Setup and run the system tray icon --- - icon_path = r"d:\Proyectos\Scripts\ParamManagerScripts\icon.png" # Use absolute path + icon_path = ( + r"d:\Proyectos\Scripts\ParamManagerScripts\icon.png" # Use absolute path + ) try: image = Image.open(icon_path) menu = pystray.Menu( pystray.MenuItem("Abrir ParamManager", open_app_browser, default=True), - pystray.MenuItem("Salir", exit_application) + pystray.MenuItem("Salir", exit_application), ) tray_icon = pystray.Icon("ParamManager", image, "ParamManager", menu) print("Starting system tray icon...") - tray_icon.run() # This blocks the main thread until icon.stop() is called + tray_icon.run() # This blocks the main thread until icon.stop() is called except FileNotFoundError: - print(f"Error: Icono no encontrado en '{icon_path}'. El icono de notificación no se iniciará.", file=sys.stderr) - print("La aplicación Flask seguirá ejecutándose en segundo plano. Presiona Ctrl+C para detenerla si es necesario.") + print( + f"Error: Icono no encontrado en '{icon_path}'. El icono de notificación no se iniciará.", + file=sys.stderr, + ) + print( + "La aplicación Flask seguirá ejecutándose en segundo plano. Presiona Ctrl+C para detenerla si es necesario." + ) # Keep the main thread alive so the Flask thread doesn't exit immediately # This allows Flask to continue running even without the tray icon. try: while flask_thread.is_alive(): - flask_thread.join(timeout=1.0) # Wait indefinitely + flask_thread.join(timeout=1.0) # Wait indefinitely except KeyboardInterrupt: print("\nCtrl+C detectado. Intentando detener Flask...") - shutdown_flask_server() # Try to shutdown Flask on Ctrl+C too + shutdown_flask_server() # Try to shutdown Flask on Ctrl+C too print("Saliendo.") except Exception as e: print(f"Error al iniciar el icono de notificación: {e}", file=sys.stderr) diff --git a/backend/script_groups/IO_adaptation/data.json b/backend/script_groups/IO_adaptation/data.json index a26700b..5fda70d 100644 --- a/backend/script_groups/IO_adaptation/data.json +++ b/backend/script_groups/IO_adaptation/data.json @@ -1,4 +1,4 @@ { - "Input_level_1": "Inputs", - "Input_level_2": "IO Not in Hardware\\\\InputsMaster" + "ObsideanDir": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM", + "ObsideanProjectsBase": "\\04-SIDEL" } \ No newline at end of file diff --git a/backend/script_groups/IO_adaptation/esquema_group.json b/backend/script_groups/IO_adaptation/esquema_group.json index bf1c958..97febac 100644 --- a/backend/script_groups/IO_adaptation/esquema_group.json +++ b/backend/script_groups/IO_adaptation/esquema_group.json @@ -1,17 +1,18 @@ { "type": "object", "properties": { - "Input_level_1": { + "ObsideanDir": { "type": "string", - "title": "Inputs", - "description": "Inputs", - "default": "Inputs" + "title": "Directorio de Vault de Obsidean", + "description": "Directorio de Vault de Obsidean", + "format": "directory", + "default": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM" }, - "Input_level_2": { + "ObsideanProjectsBase": { "type": "string", - "title": "Inputs Nivel 2", - "description": "Inputs Nivel 2", - "default": "IO Not in Hardware\\\\InputsMaster" + "title": "Subdirectorio", + "description": "Subdirectorio de los proyectos actuales en el Vault de Obsidean", + "default": "\\04-SIDEL" } } } \ No newline at end of file diff --git a/backend/script_groups/IO_adaptation/log_x0_documentation.txt b/backend/script_groups/IO_adaptation/log_x0_documentation.txt new file mode 100644 index 0000000..71c8e84 --- /dev/null +++ b/backend/script_groups/IO_adaptation/log_x0_documentation.txt @@ -0,0 +1,14 @@ +--- Log de Ejecución: x0_documentation.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 13:45:10 +Fin: 2025-05-15 13:45:10 +Duración: 0:00:00.048217 +Estado: SUCCESS (Código de Salida: 0) + +--- SALIDA ESTÁNDAR (STDOUT) --- + + +--- ERRORES (STDERR) --- +Ninguno +--- FIN DEL LOG --- diff --git a/backend/script_groups/IO_adaptation/log_x4_prompt_generator.txt b/backend/script_groups/IO_adaptation/log_x4_prompt_generator.txt index 93a1422..53bad48 100644 --- a/backend/script_groups/IO_adaptation/log_x4_prompt_generator.txt +++ b/backend/script_groups/IO_adaptation/log_x4_prompt_generator.txt @@ -1,19 +1,20 @@ --- Log de Ejecución: x4_prompt_generator.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 12:09:38 -Fin: 2025-05-15 12:09:38 -Duración: 0:00:00.084182 -Estado: ERROR (Código de Salida: 1) +Inicio: 2025-05-15 14:05:02 +Fin: 2025-05-15 14:05:04 +Duración: 0:00:01.643930 +Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- - +Generador de prompt para adaptación de IO +========================================= +Usando directorio de trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 +Usando ruta de Obsidian desde configuración: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO +Usando carpeta de equivalencias en Obsidian: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO +¡Prompt generado y copiado al portapapeles con éxito! +Prompt guardado en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\IO_Adaptation_Prompt.txt --- ERRORES (STDERR) --- -Traceback (most recent call last): - File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\x4_prompt_generator.py", line 11, in - import pyperclip # Para copiar al portapapeles - ^^^^^^^^^^^^^^^^ -ModuleNotFoundError: No module named 'pyperclip' - +Ninguno --- FIN DEL LOG --- diff --git a/backend/script_groups/IO_adaptation/script_config.json b/backend/script_groups/IO_adaptation/script_config.json index d5bf4dd..5b48ed3 100644 --- a/backend/script_groups/IO_adaptation/script_config.json +++ b/backend/script_groups/IO_adaptation/script_config.json @@ -4,8 +4,8 @@ "model": "gpt-3.5-turbo" }, "level2": { - "Input_level_1": "Inputs", - "Input_level_2": "IO Not in Hardware\\\\InputsMaster" + "ObsideanDir": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM", + "ObsideanProjectsBase": "\\04-SIDEL" }, "level3": {}, "working_directory": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2" diff --git a/backend/script_groups/IO_adaptation/x4_prompt_generator.py b/backend/script_groups/IO_adaptation/x4_prompt_generator.py index c1cd3f2..39369a9 100644 --- a/backend/script_groups/IO_adaptation/x4_prompt_generator.py +++ b/backend/script_groups/IO_adaptation/x4_prompt_generator.py @@ -9,6 +9,7 @@ import sys import tkinter as tk from tkinter import filedialog, messagebox import pyperclip # Para copiar al portapapeles +import shutil # Para copiar archivos # Determine script_root and add to sys.path for custom module import try: @@ -32,6 +33,7 @@ except NameError: # __file__ is not defined ) sys.exit(1) + def verify_path(path, is_file=True): """Verifica si una ruta existe y es un archivo o directorio según corresponda.""" if is_file: @@ -39,18 +41,19 @@ def verify_path(path, is_file=True): else: return os.path.isdir(path) + def select_obsidian_folder(): """Permite al usuario seleccionar la carpeta base de Obsidian.""" root = tk.Tk() root.withdraw() # Ocultar ventana principal - + folder_path = filedialog.askdirectory( - title="Seleccione la carpeta base de Obsidian", - mustexist=True + title="Seleccione la carpeta base de Obsidian", mustexist=True ) - + return folder_path if folder_path else None + def generate_prompt(): """ Genera el prompt para la adaptación de IO y lo copia al portapapeles. @@ -74,51 +77,101 @@ def generate_prompt(): working_directory_abs = os.path.abspath(working_directory) print(f"Usando directorio de trabajo: {working_directory_abs}") - - # Pedir al usuario que seleccione la carpeta base de Obsidian - print("Por favor, seleccione la carpeta base de Obsidian para los archivos de equivalencias...") - obsidian_base_folder = select_obsidian_folder() - - if not obsidian_base_folder: + + # Variables para las rutas de Obsidian + obsidian_dir = None + obsidian_projects_base = None + obsidian_io_path = None + + # Intentar obtener la carpeta base de Obsidian desde la configuración + try: + # Obtener configuración del nivel 2 + group_config = configs.get("level2", {}) + obsidian_dir = group_config.get("ObsideanDir") + obsidian_projects_base = group_config.get("ObsideanProjectsBase") + + if obsidian_dir and obsidian_projects_base: + # Path para la carpeta de equivalencias (00 - MASTER/MIXER/IO) + obsidian_mixer_path = os.path.join( + obsidian_dir, + obsidian_projects_base.lstrip("\\"), + "00 - MASTER", + "MIXER", + "IO", + ) + + # Path para la carpeta IO donde se copiará el archivo de salida + obsidian_io_path = os.path.join( + obsidian_dir, obsidian_projects_base.lstrip("\\"), "IO" + ) + + # Verificar que la ruta de equivalencias exista + if os.path.isdir(obsidian_mixer_path): + print( + f"Usando ruta de Obsidian desde configuración: {obsidian_mixer_path}" + ) + else: + print( + f"Ruta de Obsidian para equivalencias no válida: {obsidian_mixer_path}" + ) + obsidian_mixer_path = None + + # Crear la carpeta IO si no existe + if not os.path.isdir(obsidian_io_path): + try: + os.makedirs(obsidian_io_path) + print(f"Creada carpeta para salida en Obsidian: {obsidian_io_path}") + except Exception as e: + print(f"No se pudo crear carpeta IO en Obsidian: {e}") + obsidian_io_path = None + else: + print("Configuración incompleta para las rutas de Obsidian") + except Exception as e: + print(f"Error al leer configuración de Obsidian: {e}") + + # Si no se pudo obtener la carpeta desde la configuración, pedirla al usuario + if not obsidian_mixer_path: + print( + "Carpeta de equivalencias en Obsidian no encontrada en configuración o no válida." + ) + print( + "Por favor, seleccione la carpeta base de Obsidian para los archivos de equivalencias..." + ) + obsidian_mixer_path = select_obsidian_folder() + + if not obsidian_mixer_path: print("No se seleccionó ninguna carpeta. Saliendo...") return False - - print(f"Usando carpeta base de Obsidian: {obsidian_base_folder}") - + + print(f"Usando carpeta de equivalencias en Obsidian: {obsidian_mixer_path}") + # Definir las rutas a los archivos master_table_path = os.path.join(working_directory_abs, "Master IO Tags.md") hardware_table_path = os.path.join(working_directory_abs, "Hardware.md") adaptation_table_path = os.path.join(working_directory_abs, "IO Adapted.md") - + # Rutas a los archivos de datos semánticos - # Intentamos encontrar automáticamente la ruta correcta - mixer_io_path = os.path.join(obsidian_base_folder, "00 - MASTER", "MIXER", "IO") - - # Si no existe esta ruta, permitimos seleccionar manualmente - if not verify_path(mixer_io_path, is_file=False): - print("Ruta a la carpeta IO no encontrada. Por favor, seleccione la carpeta IO:") - root = tk.Tk() - root.withdraw() - mixer_io_path = filedialog.askdirectory( - title="Seleccione la carpeta IO dentro de la estructura de Obsidian", - mustexist=True - ) - - if not mixer_io_path: - print("No se seleccionó ninguna carpeta IO. Saliendo...") - return False - - equivalences_data_path = os.path.join(mixer_io_path, "SIDEL - Mixer - Equivalences.md") - default_io_data_path = os.path.join(mixer_io_path, "Default IO for Analog.md") - + equivalences_data_path = os.path.join( + obsidian_mixer_path, "SIDEL - Mixer - Equivalences.md" + ) + default_io_data_path = os.path.join(obsidian_mixer_path, "Default IO for Analog.md") + # Verificar que los archivos existan files_to_check = [ {"path": master_table_path, "name": "Master IO Tags.md", "required": True}, {"path": hardware_table_path, "name": "Hardware.md", "required": True}, - {"path": equivalences_data_path, "name": "SIDEL - Mixer - Equivalences.md", "required": False}, - {"path": default_io_data_path, "name": "Default IO for Analog.md", "required": False} + { + "path": equivalences_data_path, + "name": "SIDEL - Mixer - Equivalences.md", + "required": False, + }, + { + "path": default_io_data_path, + "name": "Default IO for Analog.md", + "required": False, + }, ] - + missing_files = [] for file_info in files_to_check: if not verify_path(file_info["path"]): @@ -126,19 +179,21 @@ def generate_prompt(): missing_files.append(f"[REQUERIDO] {file_info['name']}") else: missing_files.append(f"[OPCIONAL] {file_info['name']}") - + if missing_files: print("Los siguientes archivos no se encontraron:") for missing_file in missing_files: print(f" - {missing_file}") - + # Si faltan archivos requeridos, preguntar si se quiere continuar if any(f.startswith("[REQUERIDO]") for f in missing_files): - if not messagebox.askyesno("Archivos faltantes", - "Faltan archivos requeridos. ¿Desea continuar de todos modos?"): + if not messagebox.askyesno( + "Archivos faltantes", + "Faltan archivos requeridos. ¿Desea continuar de todos modos?", + ): print("Operación cancelada.") return False - + # Generar el texto del prompt prompt_text = f""" Estoy adaptando las entradas y salidas entre un hardware de PLC Siemens Tia Portal y un software master. Para lograr identificar que tags del software master se deben asignar a cada IO del hardware del PLC. Se debe asignar a cada IO del harware un Tag del software master. @@ -157,7 +212,7 @@ Para acceder a los archivos para leer o escribir puedes usar el MCP filesystem. # Definiciones de rutas $Working_Directory = "{working_directory_abs}" -$Obsidean_Base_Folder = "{obsidian_base_folder}" +$Obsidean_Base_Folder = "{obsidian_mixer_path}" # Archivos de entrada $Master_table = $Working_Directory + "/Master IO Tags.md" @@ -246,31 +301,105 @@ Para entradas con nivel de certeza medio o bajo, añade hasta 3 tags alternativo # EXCEPCIONES Al final del documento, crea una sección titulada "## Excepciones y Problemas" con una tabla que liste las IO sin asignación clara y el problema detectado. """ - + # Copiar el texto al portapapeles try: pyperclip.copy(prompt_text) print("¡Prompt generado y copiado al portapapeles con éxito!") - + # Guardar el prompt en un archivo para referencia - prompt_file_path = os.path.join(working_directory_abs, "IO_Adaptation_Prompt.txt") + prompt_file_path = os.path.join( + working_directory_abs, "IO_Adaptation_Prompt.txt" + ) with open(prompt_file_path, "w", encoding="utf-8") as f: f.write(prompt_text) print(f"Prompt guardado en: {prompt_file_path}") - - # Mostrar mensaje de éxito - messagebox.showinfo("Éxito", - f"Prompt generado y copiado al portapapeles.\n\n" - f"También se ha guardado en:\n{prompt_file_path}") - + + # También copiar el archivo IO Adapted.md a la carpeta de Obsidian cuando se genere + if obsidian_io_path: + # Crear un mensaje para informar al usuario + copy_message = "" + + # Explicar que la copia se realizará cuando se genere el archivo + copy_message = ( + f"Se copiará automáticamente el archivo 'IO Adapted.md' a:\n" + f"{obsidian_io_path}\n\n" + f"cuando sea generado por la herramienta." + ) + + # Añadir la información sobre la copia al mensaje de éxito + success_message = ( + f"Prompt generado y copiado al portapapeles.\n\n" + f"También se ha guardado en:\n{prompt_file_path}\n\n" + f"{copy_message}" + ) + + messagebox.showinfo("Éxito", success_message) + else: + # Mostrar mensaje de éxito sin la parte de copia + messagebox.showinfo( + "Éxito", + f"Prompt generado y copiado al portapapeles.\n\n" + f"También se ha guardado en:\n{prompt_file_path}", + ) + return True except Exception as e: print(f"Error al copiar al portapapeles: {e}") - messagebox.showerror("Error", - f"Error al copiar al portapapeles: {e}\n\n" - f"Por favor, instale pyperclip con 'pip install pyperclip'") + messagebox.showerror( + "Error", + f"Error al copiar al portapapeles: {e}\n\n" + f"Por favor, instale pyperclip con 'pip install pyperclip'", + ) return False + +# Agregar una función para copiar el archivo adaptado a Obsidian +def copy_adapted_file_to_obsidian(): + """Copia el archivo IO Adapted.md al directorio de IO en Obsidian si existe""" + try: + configs = load_configuration() + working_directory = configs.get("working_directory") + if not working_directory: + return False + + # Verificar si el archivo existe en el directorio de trabajo + source_file = os.path.join(working_directory, "IO Adapted.md") + if not os.path.isfile(source_file): + print(f"Archivo '{source_file}' no encontrado.") + return False + + # Obtener la ruta de destino en Obsidian + group_config = configs.get("level2", {}) + obsidian_dir = group_config.get("ObsideanDir") + obsidian_projects_base = group_config.get("ObsideanProjectsBase") + + if not obsidian_dir or not obsidian_projects_base: + print("Configuración de Obsidian incompleta.") + return False + + # Construir la ruta de destino + obsidian_io_path = os.path.join( + obsidian_dir, obsidian_projects_base.lstrip("\\"), "IO" + ) + + # Crear la carpeta si no existe + if not os.path.exists(obsidian_io_path): + os.makedirs(obsidian_io_path) + + # Definir el destino + dest_file = os.path.join(obsidian_io_path, "IO Adapted.md") + + # Copiar el archivo + shutil.copy2(source_file, dest_file) + print(f"Archivo copiado exitosamente a: {dest_file}") + + return True + except Exception as e: + print(f"Error al copiar archivo a Obsidian: {e}") + return False + + if __name__ == "__main__": print("Generador de prompt para adaptación de IO") print("=========================================") diff --git a/data/log.txt b/data/log.txt index 194705d..4fb7b47 100644 --- a/data/log.txt +++ b/data/log.txt @@ -1,19 +1,10 @@ -[11:58:03] Iniciando ejecución de x3_excel_to_md.py en C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2... -[11:58:04] Usando directorio de trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 -[11:58:04] Configuración de paths cargada desde: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\io_paths_config.json -[11:58:04] Usando archivo Excel predeterminado: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLCTags.xlsx -[11:58:04] Procesando archivo Excel: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\PLCTags.xlsx... -[11:58:04] Paths configurados para procesar: ['Inputs', 'Outputs', 'OutputsFesto', 'IO Not in Hardware\\InputsMaster', 'IO Not in Hardware\\OutputsMaster'] -[11:58:05] ¡Éxito! Archivo Excel convertido a Markdown en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\Master IO Tags.md -[11:58:05] Ejecución de x3_excel_to_md.py finalizada (success). Duración: 0:00:01.664065. -[11:58:05] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x3_excel_to_md.txt -[12:09:38] Iniciando ejecución de x4_prompt_generator.py en C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2... -[12:09:38] --- ERRORES --- -[12:09:38] Traceback (most recent call last): -[12:09:38] File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\x4_prompt_generator.py", line 11, in -[12:09:38] import pyperclip # Para copiar al portapapeles -[12:09:38] ^^^^^^^^^^^^^^^^ -[12:09:38] ModuleNotFoundError: No module named 'pyperclip' -[12:09:38] --- FIN ERRORES --- -[12:09:38] Ejecución de x4_prompt_generator.py finalizada (error). Duración: 0:00:00.084182. Se detectaron errores (ver log). -[12:09:38] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x4_prompt_generator.txt +[14:05:02] Iniciando ejecución de x4_prompt_generator.py en C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2... +[14:05:03] Generador de prompt para adaptación de IO +[14:05:03] ========================================= +[14:05:03] Usando directorio de trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2 +[14:05:03] Usando ruta de Obsidian desde configuración: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO +[14:05:03] Usando carpeta de equivalencias en Obsidian: C:\Users\migue\OneDrive\Miguel\Obsidean\Trabajo\VM\04-SIDEL\00 - MASTER\MIXER\IO +[14:05:03] ¡Prompt generado y copiado al portapapeles con éxito! +[14:05:03] Prompt guardado en: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\TAGsIO\v2\IO_Adaptation_Prompt.txt +[14:05:04] Ejecución de x4_prompt_generator.py finalizada (success). Duración: 0:00:01.643930. +[14:05:04] Log completo guardado en: d:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x4_prompt_generator.txt diff --git a/static/js/scripts.js b/static/js/scripts.js index 74f4ffc..cfe74a5 100644 --- a/static/js/scripts.js +++ b/static/js/scripts.js @@ -1215,8 +1215,6 @@ function showToast(message, type = 'success') { // Llama a fetchLogs al cargar la página si es necesario // document.addEventListener('DOMContentLoaded', fetchLogs); - - // Agregar función para guardar configuración async function saveConfig(level) { const saveButton = document.getElementById(`save-config-${level}`); @@ -1270,4 +1268,50 @@ async function saveConfig(level) { } }, 2000); } +} + +async function openGroupInVsCode() { + if (!currentGroup) { + alert('Por favor, seleccione un grupo de scripts primero'); + return; + } + + try { + const response = await fetch(`/api/open-vscode/${currentGroup}`, { + method: 'POST' + }); + + const result = await response.json(); + + if (result.status === 'success') { + console.log('VS Code opened successfully'); + } else { + console.error('Error opening VS Code:', result.message); + alert(`Error al abrir VS Code: ${result.message}`); + } + } catch (error) { + console.error('Error calling open-vscode API:', error); + alert('Error al intentar abrir VS Code'); + } +} + +function openMinicondaConsole() { + fetch('/api/open-miniconda', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + if (data.status === 'success') { + showNotification('Miniconda Console abierta correctamente', 'success'); + } else { + showNotification(`Error al abrir Miniconda Console: ${data.message}`, 'error'); + } + }) + .catch(error => { + console.error('Error opening Miniconda Console:', error); + showNotification('Error al comunicarse con el servidor', 'error'); + }); } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 7304d24..f21e408 100644 --- a/templates/index.html +++ b/templates/index.html @@ -71,6 +71,9 @@
+ @@ -94,6 +97,11 @@ +