Varias mejoras en el grupo de IO Adapted. Creado boton para abrir vscode y miniconda para instalar nuevas librerias

This commit is contained in:
Miguel 2025-05-15 14:11:24 +02:00
parent f6ae6f4f82
commit 7f5d7ff033
10 changed files with 463 additions and 120 deletions

197
app.py
View File

@ -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/<group>", 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)

View File

@ -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"
}

View File

@ -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"
}
}
}

View File

@ -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 ---

View File

@ -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 <module>
import pyperclip # Para copiar al portapapeles
^^^^^^^^^^^^^^^^
ModuleNotFoundError: No module named 'pyperclip'
Ninguno
--- FIN DEL LOG ---

View File

@ -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"

View File

@ -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("=========================================")

View File

@ -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 <module>
[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

View File

@ -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');
});
}

View File

@ -71,6 +71,9 @@
<!-- Botón para detener el servidor -->
<div class="mt-8 pt-4 border-t border-gray-300">
<button class="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow mb-2" onclick="openMinicondaConsole()">
Abrir Miniconda Console
</button>
<button class="w-full bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded shadow" onclick="shutdownServer()">
Detener Servidor
</button>
@ -94,6 +97,11 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</button>
<button onclick="openGroupInVsCode()" class="bg-blue-500 text-white p-2 rounded mb-2" title="Abrir grupo en VS Code">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
</button>
</div>
<p id="group-description" class="text-gray-600 text-sm italic"></p>
<div class="text-xs text-gray-500 mt-2">