Primera version funcionando
This commit is contained in:
commit
d92eeb8c75
Binary file not shown.
|
@ -0,0 +1,151 @@
|
|||
from flask import Flask, render_template, request, jsonify
|
||||
from flask_sock import Sock
|
||||
from config_manager import ConfigurationManager
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
sock = Sock(app)
|
||||
config_manager = ConfigurationManager()
|
||||
|
||||
# Lista global para mantener las conexiones WebSocket activas
|
||||
websocket_connections = set()
|
||||
|
||||
|
||||
@sock.route("/ws")
|
||||
def handle_websocket(ws):
|
||||
try:
|
||||
websocket_connections.add(ws)
|
||||
while True:
|
||||
message = ws.receive()
|
||||
if message:
|
||||
broadcast_message(message)
|
||||
except Exception as e:
|
||||
print(f"WebSocket error: {e}")
|
||||
finally:
|
||||
websocket_connections.remove(ws)
|
||||
|
||||
|
||||
def broadcast_message(message):
|
||||
"""Envía un mensaje a todas las conexiones WebSocket activas."""
|
||||
dead_connections = set()
|
||||
for ws in websocket_connections:
|
||||
try:
|
||||
ws.send(message)
|
||||
except Exception:
|
||||
dead_connections.add(ws)
|
||||
|
||||
# Limpiar conexiones muertas
|
||||
websocket_connections.difference_update(dead_connections)
|
||||
|
||||
|
||||
@app.route("/api/execute_script", methods=["POST"])
|
||||
def execute_script():
|
||||
try:
|
||||
script_group = request.json["group"]
|
||||
script_name = request.json["script"]
|
||||
|
||||
# Ejecutar el script y obtener resultado
|
||||
result = config_manager.execute_script(
|
||||
script_group, script_name, broadcast_message
|
||||
)
|
||||
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
error_msg = f"Error ejecutando script: {str(e)}"
|
||||
broadcast_message(error_msg)
|
||||
return jsonify({"error": error_msg})
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
script_groups = config_manager.get_script_groups()
|
||||
return render_template("index.html", script_groups=script_groups)
|
||||
|
||||
|
||||
@app.route("/api/config/<level>", methods=["GET", "POST"])
|
||||
def handle_config(level):
|
||||
group = request.args.get("group")
|
||||
if request.method == "GET":
|
||||
try:
|
||||
return jsonify(config_manager.get_config(level, group))
|
||||
except FileNotFoundError:
|
||||
return jsonify({})
|
||||
else:
|
||||
data = request.json
|
||||
config_manager.update_config(level, data, group)
|
||||
return jsonify({"status": "success"})
|
||||
|
||||
|
||||
@app.route("/api/schema/<level>", methods=["GET", "POST"])
|
||||
def handle_schema(level):
|
||||
group = request.args.get("group")
|
||||
if request.method == "GET":
|
||||
return jsonify(config_manager.get_schema(level, group))
|
||||
else:
|
||||
data = request.json
|
||||
config_manager.update_schema(level, data, group)
|
||||
return jsonify({"status": "success"})
|
||||
|
||||
|
||||
@app.route("/api/scripts/<group>")
|
||||
def get_scripts(group):
|
||||
return jsonify(config_manager.list_scripts(group))
|
||||
|
||||
|
||||
@app.route("/api/working-directory", methods=["POST"])
|
||||
def set_working_directory():
|
||||
data = request.json
|
||||
if not data:
|
||||
return jsonify({"status": "error", "message": "No data provided"})
|
||||
|
||||
path = data.get("path")
|
||||
group = data.get("group")
|
||||
|
||||
if not path or not group:
|
||||
return jsonify(
|
||||
{
|
||||
"status": "error",
|
||||
"message": f"Missing required fields. Path: {path}, Group: {group}",
|
||||
}
|
||||
)
|
||||
|
||||
print(f"Setting working directory - Path: {path}, Group: {group}") # Debug line
|
||||
return jsonify(config_manager.set_work_dir(group, path))
|
||||
|
||||
|
||||
@app.route("/api/working-directory/<group>", methods=["GET"])
|
||||
def get_working_directory(group):
|
||||
path = config_manager.get_work_dir(group)
|
||||
return jsonify({"path": path, "status": "success" if path else "not_set"})
|
||||
|
||||
|
||||
@app.route("/api/browse-directories")
|
||||
def browse_directories():
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog
|
||||
|
||||
# Obtener el directorio inicial
|
||||
current_dir = request.args.get("current_path")
|
||||
if not current_dir or not os.path.exists(current_dir):
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Crear y configurar la ventana principal de tkinter
|
||||
root = tk.Tk()
|
||||
root.attributes("-topmost", True) # Mantener la ventana siempre arriba
|
||||
root.withdraw()
|
||||
|
||||
# Abrir el diálogo de selección de directorio
|
||||
directory = filedialog.askdirectory(
|
||||
initialdir=current_dir, title="Seleccionar Directorio de Trabajo"
|
||||
)
|
||||
|
||||
# Destruir la ventana de tkinter
|
||||
root.destroy()
|
||||
|
||||
if directory:
|
||||
return jsonify({"status": "success", "path": directory})
|
||||
return jsonify({"status": "cancelled"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"properties": {
|
||||
"batch_size": {
|
||||
"description": "N\u00c3\u00bamero de elementos a procesar por lote",
|
||||
"title": "Tama\u00c3\u00b1o de Lote",
|
||||
"type": "number"
|
||||
},
|
||||
"input_dir": {
|
||||
"description": "Ruta al directorio de archivos de entrada",
|
||||
"title": "Directorio de Entrada",
|
||||
"type": "string"
|
||||
},
|
||||
"output_dir": {
|
||||
"description": "Ruta al directorio para archivos generados",
|
||||
"title": "Directorio de Salida",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"input_dir": "D:/Datos/Entrada",
|
||||
"output_dir": "D:/Datos/Salida",
|
||||
"batch_size": 50
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_name": {
|
||||
"type": "string",
|
||||
"title": "Nombre del Proyecto",
|
||||
"description": "Identificador único del proyecto"
|
||||
},
|
||||
"process_type": {
|
||||
"type": "string",
|
||||
"title": "Tipo de Proceso",
|
||||
"enum": ["basic", "advanced", "full"],
|
||||
"description": "Nivel de procesamiento a aplicar"
|
||||
},
|
||||
"debug_mode": {
|
||||
"type": "boolean",
|
||||
"title": "Modo Debug",
|
||||
"description": "Activar logging detallado"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"path": "C:/Estudio"
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
"""
|
||||
Script de prueba que imprime las configuraciones y realiza una tarea simple.
|
||||
Este script demuestra cómo acceder a las configuraciones de los tres niveles.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
def main():
|
||||
# Cargar configuraciones desde variable de entorno
|
||||
configs = json.loads(os.environ.get('SCRIPT_CONFIGS', '{}'))
|
||||
|
||||
print("=== Ejecutando Script de Prueba 1 ===")
|
||||
print("\nConfiguraciones cargadas:")
|
||||
print("Nivel 1:", json.dumps(configs.get('level1', {}), indent=2))
|
||||
print("Nivel 2:", json.dumps(configs.get('level2', {}), indent=2))
|
||||
print("Nivel 3:", json.dumps(configs.get('level3', {}), indent=2))
|
||||
|
||||
print("\nSimulando procesamiento...")
|
||||
for i in range(5):
|
||||
time.sleep(1)
|
||||
print(f"Progreso: {(i+1)*20}%")
|
||||
|
||||
print("\n¡Proceso completado!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Script de prueba que simula un proceso de análisis de datos.
|
||||
Demuestra el manejo de errores y logging.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
|
||||
def main():
|
||||
configs = json.loads(os.environ.get('SCRIPT_CONFIGS', '{}'))
|
||||
|
||||
print("=== Ejecutando Script de Prueba 2 ===")
|
||||
print("\nIniciando análisis de datos simulado...")
|
||||
|
||||
# Simular proceso con posible error
|
||||
try:
|
||||
for i in range(3):
|
||||
time.sleep(1)
|
||||
print(f"Analizando lote {i+1}...")
|
||||
|
||||
# Simular error aleatorio
|
||||
if random.random() < 0.3:
|
||||
raise Exception("Error simulado en el procesamiento")
|
||||
|
||||
print(f"Lote {i+1} completado exitosamente")
|
||||
|
||||
print("\n¡Análisis completado sin errores!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nERROR: {str(e)}")
|
||||
print("El proceso se detuvo debido a un error")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,171 @@
|
|||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
class ClaudeProjectOrganizer:
|
||||
def __init__(self):
|
||||
self.source_dir = Path.cwd()
|
||||
self.claude_dir = self.source_dir / 'claude'
|
||||
self.file_mapping = {}
|
||||
|
||||
def should_skip_directory(self, dir_name):
|
||||
skip_dirs = {'.git', '__pycache__', 'venv', 'env', '.pytest_cache', '.vscode', 'claude'}
|
||||
return dir_name in skip_dirs
|
||||
|
||||
def get_comment_prefix(self, file_extension):
|
||||
"""Determina el prefijo de comentario según la extensión del archivo"""
|
||||
comment_styles = {
|
||||
'.py': '#',
|
||||
'.js': '//',
|
||||
'.css': '/*',
|
||||
'.html': '<!--',
|
||||
'.scss': '//',
|
||||
'.less': '//',
|
||||
'.tsx': '//',
|
||||
'.ts': '//',
|
||||
'.jsx': '//',
|
||||
}
|
||||
return comment_styles.get(file_extension.lower(), None)
|
||||
|
||||
def get_comment_suffix(self, file_extension):
|
||||
"""Determina el sufijo de comentario si es necesario"""
|
||||
comment_suffixes = {
|
||||
'.css': ' */',
|
||||
'.html': ' -->',
|
||||
}
|
||||
return comment_suffixes.get(file_extension.lower(), '')
|
||||
|
||||
def normalize_path(self, path_str: str) -> str:
|
||||
"""Normaliza la ruta usando forward slashes"""
|
||||
return str(path_str).replace('\\', '/')
|
||||
|
||||
def check_existing_path_comment(self, content: str, normalized_path: str, comment_prefix: str) -> bool:
|
||||
"""Verifica si ya existe un comentario con la ruta en el archivo"""
|
||||
# Escapar caracteres especiales en el prefijo de comentario para regex
|
||||
escaped_prefix = re.escape(comment_prefix)
|
||||
|
||||
# Crear patrones para buscar tanto forward como backward slashes
|
||||
forward_pattern = f"{escaped_prefix}\\s*{re.escape(normalized_path)}\\b"
|
||||
backward_path = normalized_path.replace('/', '\\\\') # Doble backslash para el patrón
|
||||
backward_pattern = f"{escaped_prefix}\\s*{re.escape(backward_path)}"
|
||||
|
||||
# Buscar en las primeras líneas del archivo
|
||||
first_lines = content.split('\n')[:5]
|
||||
for line in first_lines:
|
||||
if (re.search(forward_pattern, line) or
|
||||
re.search(backward_pattern, line)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_path_comment(self, file_path: Path, content: str) -> str:
|
||||
"""Agrega un comentario con la ruta al inicio del archivo si no existe"""
|
||||
relative_path = file_path.relative_to(self.source_dir)
|
||||
normalized_path = self.normalize_path(relative_path)
|
||||
comment_prefix = self.get_comment_prefix(file_path.suffix)
|
||||
|
||||
if comment_prefix is None:
|
||||
return content
|
||||
|
||||
comment_suffix = self.get_comment_suffix(file_path.suffix)
|
||||
|
||||
# Verificar si ya existe el comentario
|
||||
if self.check_existing_path_comment(content, normalized_path, comment_prefix):
|
||||
print(f" - Comentario de ruta ya existe en {file_path}")
|
||||
return content
|
||||
|
||||
path_comment = f"{comment_prefix} {normalized_path}{comment_suffix}\n"
|
||||
|
||||
# Para archivos HTML, insertar después del doctype si existe
|
||||
if file_path.suffix.lower() == '.html':
|
||||
if content.lower().startswith('<!doctype'):
|
||||
doctype_end = content.find('>') + 1
|
||||
return content[:doctype_end] + '\n' + path_comment + content[doctype_end:]
|
||||
|
||||
return path_comment + content
|
||||
|
||||
def clean_claude_directory(self):
|
||||
if self.claude_dir.exists():
|
||||
shutil.rmtree(self.claude_dir)
|
||||
self.claude_dir.mkdir()
|
||||
print(f"Directorio claude limpiado: {self.claude_dir}")
|
||||
|
||||
def copy_files(self):
|
||||
self.clean_claude_directory()
|
||||
|
||||
for root, dirs, files in os.walk(self.source_dir):
|
||||
dirs[:] = [d for d in dirs if not self.should_skip_directory(d)]
|
||||
current_path = Path(root)
|
||||
|
||||
for file in files:
|
||||
file_path = current_path / file
|
||||
|
||||
if file.endswith(('.py', '.js', '.css', '.html', '.json', '.yml', '.yaml',
|
||||
'.tsx', '.ts', '.jsx', '.scss', '.less')):
|
||||
target_path = self.claude_dir / file
|
||||
|
||||
# Si el archivo ya existe en el directorio claude, agregar un sufijo numérico
|
||||
if target_path.exists():
|
||||
base = target_path.stem
|
||||
ext = target_path.suffix
|
||||
counter = 1
|
||||
while target_path.exists():
|
||||
target_path = self.claude_dir / f"{base}_{counter}{ext}"
|
||||
counter += 1
|
||||
|
||||
try:
|
||||
# Leer el contenido del archivo
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Agregar el comentario con la ruta si no existe
|
||||
modified_content = self.add_path_comment(file_path, content)
|
||||
|
||||
# Escribir el nuevo contenido
|
||||
with open(target_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(modified_content)
|
||||
|
||||
self.file_mapping[str(file_path)] = target_path.name
|
||||
print(f"Copiado: {file_path} -> {target_path}")
|
||||
|
||||
except UnicodeDecodeError:
|
||||
print(f"Advertencia: No se pudo procesar {file_path} como texto. Copiando sin modificar...")
|
||||
shutil.copy2(file_path, target_path)
|
||||
except Exception as e:
|
||||
print(f"Error procesando {file_path}: {str(e)}")
|
||||
|
||||
def generate_tree_report(self):
|
||||
"""Genera el reporte en formato árbol visual"""
|
||||
report = ["Estructura del proyecto original:\n"]
|
||||
|
||||
def add_to_report(path, prefix="", is_last=True):
|
||||
report.append(prefix + ("└── " if is_last else "├── ") + path.name)
|
||||
|
||||
if path.is_dir() and not self.should_skip_directory(path.name):
|
||||
children = sorted(path.iterdir(), key=lambda x: (x.is_file(), x.name))
|
||||
children = [c for c in children if not (c.is_dir() and self.should_skip_directory(c.name))]
|
||||
|
||||
for i, child in enumerate(children):
|
||||
is_last_child = i == len(children) - 1
|
||||
new_prefix = prefix + (" " if is_last else "│ ")
|
||||
add_to_report(child, new_prefix, is_last_child)
|
||||
|
||||
add_to_report(self.source_dir)
|
||||
|
||||
report_path = self.claude_dir / "project_structure.txt"
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(report))
|
||||
print(f"\nReporte generado en: {report_path}")
|
||||
|
||||
def main():
|
||||
try:
|
||||
print("Iniciando organización de archivos para Claude...")
|
||||
organizer = ClaudeProjectOrganizer()
|
||||
organizer.copy_files()
|
||||
organizer.generate_tree_report()
|
||||
print("\n¡Proceso completado exitosamente!")
|
||||
except Exception as e:
|
||||
print(f"\nError durante la ejecución: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,261 @@
|
|||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import re
|
||||
from typing import Dict, Any, List
|
||||
|
||||
|
||||
class ConfigurationManager:
|
||||
def __init__(self):
|
||||
self.base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
self.data_path = os.path.join(self.base_path, "data")
|
||||
self.script_groups_path = os.path.join(
|
||||
self.base_path, "backend", "script_groups"
|
||||
)
|
||||
self.working_directory = None
|
||||
|
||||
def set_working_directory(self, path: str) -> Dict[str, str]:
|
||||
"""Set and validate working directory."""
|
||||
if not os.path.exists(path):
|
||||
return {"status": "error", "message": "Directory does not exist"}
|
||||
|
||||
self.working_directory = path
|
||||
|
||||
# Create default data.json if it doesn't exist
|
||||
data_path = os.path.join(path, "data.json")
|
||||
if not os.path.exists(data_path):
|
||||
with open(data_path, "w") as f:
|
||||
json.dump({}, f, indent=2)
|
||||
|
||||
return {"status": "success", "path": path}
|
||||
|
||||
def get_script_groups(self) -> List[str]:
|
||||
"""Returns list of available script groups."""
|
||||
return [
|
||||
d
|
||||
for d in os.listdir(self.script_groups_path)
|
||||
if os.path.isdir(os.path.join(self.script_groups_path, d))
|
||||
]
|
||||
|
||||
def get_config(self, level: str, group: str = None) -> Dict[str, Any]:
|
||||
"""Get configuration for specified level."""
|
||||
if level == "1":
|
||||
path = os.path.join(self.data_path, "data.json")
|
||||
elif level == "2":
|
||||
path = os.path.join(self.script_groups_path, group, "data.json")
|
||||
elif level == "3":
|
||||
if not self.working_directory:
|
||||
return {} # Return empty config if working directory not set
|
||||
path = os.path.join(self.working_directory, "data.json")
|
||||
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
return {} # Return empty config if file doesn't exist
|
||||
|
||||
def get_schema(self, level: str, group: str = None) -> Dict[str, Any]:
|
||||
"""Get schema for specified level."""
|
||||
# Clean level parameter (remove -form suffix if present)
|
||||
level = level.split("-")[0]
|
||||
|
||||
try:
|
||||
if level == "1":
|
||||
path = os.path.join(self.data_path, "esquema.json")
|
||||
elif level == "2":
|
||||
path = os.path.join(self.script_groups_path, "esquema.json")
|
||||
elif level == "3":
|
||||
if not group:
|
||||
return {} # Return empty schema if no group is specified
|
||||
path = os.path.join(self.script_groups_path, group, "esquema.json")
|
||||
else:
|
||||
return {} # Return empty schema for invalid levels
|
||||
|
||||
with open(path, "r") as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
return {} # Return empty schema if file doesn't exist
|
||||
except json.JSONDecodeError:
|
||||
return {} # Return empty schema if file is invalid JSON
|
||||
|
||||
def update_config(
|
||||
self, level: str, data: Dict[str, Any], group: str = None
|
||||
) -> Dict[str, str]:
|
||||
"""Update configuration for specified level."""
|
||||
if level == "3" and not self.working_directory:
|
||||
return {"status": "error", "message": "Working directory not set"}
|
||||
|
||||
if level == "1":
|
||||
path = os.path.join(self.data_path, "data.json")
|
||||
elif level == "2":
|
||||
path = os.path.join(self.script_groups_path, group, "data.json")
|
||||
elif level == "3":
|
||||
path = os.path.join(self.working_directory, "data.json")
|
||||
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
def update_schema(
|
||||
self, level: str, data: Dict[str, Any], group: str = None
|
||||
) -> None:
|
||||
"""Update schema for specified level."""
|
||||
if level == "1":
|
||||
path = os.path.join(self.data_path, "esquema.json")
|
||||
elif level == "2":
|
||||
path = os.path.join(self.script_groups_path, "esquema.json")
|
||||
elif level == "3":
|
||||
path = os.path.join(self.script_groups_path, group, "esquema.json")
|
||||
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
def list_scripts(self, group: str) -> List[Dict[str, str]]:
|
||||
"""List all scripts in a group with their descriptions."""
|
||||
try:
|
||||
scripts_dir = os.path.join(self.script_groups_path, group)
|
||||
scripts = []
|
||||
|
||||
if not os.path.exists(scripts_dir):
|
||||
print(f"Directory not found: {scripts_dir}")
|
||||
return []
|
||||
|
||||
for file in os.listdir(scripts_dir):
|
||||
# Modificar la condición para incluir cualquier archivo .py
|
||||
if file.endswith(".py"):
|
||||
path = os.path.join(scripts_dir, file)
|
||||
description = self._extract_script_description(path)
|
||||
print(
|
||||
f"Found script: {file} with description: {description}"
|
||||
) # Debug line
|
||||
scripts.append({"name": file, "description": description})
|
||||
|
||||
print(f"Total scripts found: {len(scripts)}") # Debug line
|
||||
return scripts
|
||||
except Exception as e:
|
||||
print(f"Error listing scripts: {str(e)}") # Debug line
|
||||
return []
|
||||
|
||||
def _extract_script_description(self, script_path: str) -> str:
|
||||
"""Extract description from script's docstring or initial comments."""
|
||||
try:
|
||||
with open(script_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Try to find docstring
|
||||
docstring_match = re.search(r'"""(.*?)"""', content, re.DOTALL)
|
||||
if docstring_match:
|
||||
return docstring_match.group(1).strip()
|
||||
|
||||
# Try to find initial comment
|
||||
comment_match = re.search(r"^#\s*(.*?)$", content, re.MULTILINE)
|
||||
if comment_match:
|
||||
return comment_match.group(1).strip()
|
||||
|
||||
return "No description available"
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Error extracting description from {script_path}: {str(e)}"
|
||||
) # Debug line
|
||||
return "Error reading script description"
|
||||
|
||||
def execute_script(
|
||||
self, group: str, script_name: str, broadcast_fn=None
|
||||
) -> Dict[str, Any]:
|
||||
"""Execute script with real-time logging via WebSocket broadcast function."""
|
||||
script_path = os.path.join(self.script_groups_path, group, script_name)
|
||||
if not os.path.exists(script_path):
|
||||
return {"error": "Script not found"}
|
||||
|
||||
# Obtener el directorio de trabajo del grupo
|
||||
working_dir = self.get_work_dir(group)
|
||||
if not working_dir:
|
||||
return {"error": "Working directory not set for this script group"}
|
||||
|
||||
configs = {
|
||||
"level1": self.get_config("1"),
|
||||
"level2": self.get_config("2", group),
|
||||
"level3": self.get_config("3") if self.working_directory else {},
|
||||
}
|
||||
|
||||
try:
|
||||
if broadcast_fn:
|
||||
broadcast_fn(f"\nIniciando ejecución de {script_name}...\n")
|
||||
|
||||
process = subprocess.Popen(
|
||||
["python", script_path],
|
||||
cwd=working_dir or self.base_path,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
env=dict(os.environ, **{"SCRIPT_CONFIGS": json.dumps(configs)}),
|
||||
)
|
||||
|
||||
output = []
|
||||
while True:
|
||||
line = process.stdout.readline()
|
||||
if not line and process.poll() is not None:
|
||||
break
|
||||
if line:
|
||||
output.append(line)
|
||||
if broadcast_fn:
|
||||
broadcast_fn(line)
|
||||
|
||||
stderr = process.stderr.read()
|
||||
if stderr:
|
||||
if broadcast_fn:
|
||||
broadcast_fn(f"\nERROR: {stderr}\n")
|
||||
output.append(f"ERROR: {stderr}")
|
||||
|
||||
if broadcast_fn:
|
||||
broadcast_fn("\nEjecución completada.\n")
|
||||
|
||||
return {
|
||||
"output": "".join(output),
|
||||
"error": stderr if stderr else None,
|
||||
"status": "success" if process.returncode == 0 else "error",
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if broadcast_fn:
|
||||
broadcast_fn(f"\nError inesperado: {error_msg}\n")
|
||||
return {"error": error_msg}
|
||||
|
||||
def get_work_dir(self, group: str) -> str:
|
||||
"""Get working directory path for a script group."""
|
||||
work_dir_path = os.path.join(self.script_groups_path, group, "work_dir.json")
|
||||
try:
|
||||
with open(work_dir_path, "r") as f:
|
||||
data = json.load(f)
|
||||
path = data.get("path", "")
|
||||
# Actualizar la variable de instancia si hay una ruta válida
|
||||
if path and os.path.exists(path):
|
||||
self.working_directory = path
|
||||
return path
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return ""
|
||||
|
||||
def set_work_dir(self, group: str, path: str) -> Dict[str, str]:
|
||||
"""Set working directory path for a script group."""
|
||||
if not os.path.exists(path):
|
||||
return {"status": "error", "message": "Directory does not exist"}
|
||||
|
||||
work_dir_path = os.path.join(self.script_groups_path, group, "work_dir.json")
|
||||
|
||||
try:
|
||||
# Guardar la ruta en work_dir.json
|
||||
with open(work_dir_path, "w") as f:
|
||||
json.dump({"path": path}, f, indent=2)
|
||||
|
||||
# Actualizar la variable de instancia
|
||||
self.working_directory = path
|
||||
|
||||
# Crear data.json en el directorio de trabajo si no existe
|
||||
data_path = os.path.join(path, "data.json")
|
||||
if not os.path.exists(data_path):
|
||||
with open(data_path, "w") as f:
|
||||
json.dump({}, f, indent=2)
|
||||
|
||||
return {"status": "success", "path": path}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"api_key": "your-api-key-here",
|
||||
"model": "gpt-3.5-turbo",
|
||||
"max_tokens": 1000,
|
||||
"temperature": 0.7
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"description": "Tu clave de API para servicios externos",
|
||||
"title": "API Key",
|
||||
"type": "string"
|
||||
},
|
||||
"max_tokens": {
|
||||
"description": "N\u00c3\u00bamero m\u00c3\u00a1ximo de tokens por respuesta",
|
||||
"title": "Tokens M\u00c3\u00a1ximos",
|
||||
"type": "number"
|
||||
},
|
||||
"model": {
|
||||
"description": "Modelo de lenguaje a utilizar",
|
||||
"enum": [
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-4",
|
||||
"claude-v1"
|
||||
],
|
||||
"title": "Modelo LLM",
|
||||
"type": "string"
|
||||
},
|
||||
"temperature": {
|
||||
"description": "Creatividad de las respuestas (0-1)",
|
||||
"maximum": 1,
|
||||
"minimum": 0,
|
||||
"title": "Temperatura",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
|
@ -0,0 +1,504 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Script Parameter Manager</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
let socket;
|
||||
let currentGroup;
|
||||
|
||||
// Initialize WebSocket connection
|
||||
function initWebSocket() {
|
||||
socket = new WebSocket(`ws://${location.host}/ws`);
|
||||
socket.onmessage = function(event) {
|
||||
const logArea = document.getElementById('log-area');
|
||||
logArea.innerHTML += event.data;
|
||||
logArea.scrollTop = logArea.scrollHeight;
|
||||
};
|
||||
socket.onclose = function() {
|
||||
console.log('WebSocket cerrado, intentando reconexión...');
|
||||
setTimeout(initWebSocket, 1000);
|
||||
};
|
||||
socket.onerror = function(error) {
|
||||
console.error('Error en WebSocket:', error);
|
||||
};
|
||||
}
|
||||
|
||||
// Load configurations for all levels
|
||||
async function loadConfigs() {
|
||||
const group = document.getElementById('script-group').value;
|
||||
currentGroup = group;
|
||||
console.log('Loading configs for group:', group); // Debug line
|
||||
|
||||
try {
|
||||
// Cargar niveles 1 y 2
|
||||
for (let level of [1, 2]) {
|
||||
console.log(`Loading level ${level} config...`); // Debug line
|
||||
const response = await fetch(`/api/config/${level}?group=${group}`);
|
||||
const data = await response.json();
|
||||
console.log(`Level ${level} data:`, data); // Debug line
|
||||
await renderForm(`level${level}-form`, data);
|
||||
}
|
||||
|
||||
// Cargar nivel 3 solo si hay directorio de trabajo
|
||||
const workingDirResponse = await fetch(`/api/working-directory/${group}`);
|
||||
const workingDirResult = await workingDirResponse.json();
|
||||
if (workingDirResult.status === 'success' && workingDirResult.path) {
|
||||
console.log('Loading level 3 config...'); // Debug line
|
||||
const response = await fetch(`/api/config/3?group=${group}`);
|
||||
const data = await response.json();
|
||||
console.log('Level 3 data:', data); // Debug line
|
||||
await renderForm('level3-form', data);
|
||||
}
|
||||
|
||||
await loadScripts(group);
|
||||
} catch (error) {
|
||||
console.error('Error loading configs:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load and display available scripts
|
||||
async function loadScripts(group) {
|
||||
const response = await fetch(`/api/scripts/${group}`);
|
||||
const scripts = await response.json();
|
||||
const container = document.getElementById('scripts-list');
|
||||
container.innerHTML = scripts.map(script => `
|
||||
<div class="mb-4 p-4 border rounded">
|
||||
<div class="font-bold">${script.name}</div>
|
||||
<div class="text-gray-600 text-sm">${script.description}</div>
|
||||
<button onclick="executeScript('${script.name}')"
|
||||
class="mt-2 bg-green-500 text-white px-3 py-1 rounded">
|
||||
Ejecutar
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Execute a script
|
||||
async function executeScript(scriptName) {
|
||||
// Mostrar mensaje de inicio en los logs
|
||||
const logArea = document.getElementById('log-area');
|
||||
logArea.innerHTML += `\nEjecutando script: ${scriptName}...\n`;
|
||||
logArea.scrollTop = logArea.scrollHeight;
|
||||
|
||||
const response = await fetch('/api/execute_script', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ group: currentGroup, script: scriptName })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
logArea.innerHTML += `\nError: ${result.error}\n`;
|
||||
}
|
||||
logArea.scrollTop = logArea.scrollHeight;
|
||||
}
|
||||
|
||||
// Form rendering functionality
|
||||
async function renderForm(containerId, data) {
|
||||
console.log(`Rendering form for ${containerId}`); // Debug line
|
||||
const container = document.getElementById(containerId);
|
||||
const level = containerId.replace('level', '').split('-')[0];
|
||||
|
||||
try {
|
||||
const schemaResponse = await fetch(`/api/schema/${level}?group=${currentGroup}`);
|
||||
const schema = await schemaResponse.json();
|
||||
console.log(`Schema for level ${level}:`, schema); // Debug line
|
||||
|
||||
if (Object.keys(schema).length === 0) {
|
||||
container.innerHTML = '<p class="text-gray-500">No hay esquema definido para este nivel.</p>';
|
||||
} else {
|
||||
const formHtml = generateFormFields(schema, data, '', level);
|
||||
console.log(`Generated HTML for ${containerId}:`, formHtml.substring(0, 100) + '...'); // Debug line
|
||||
container.innerHTML = formHtml;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error rendering form ${containerId}:`, error);
|
||||
container.innerHTML = '<p class="text-red-500">Error cargando el esquema.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function generateFormFields(schema, data, prefix, level) {
|
||||
let html = '';
|
||||
for (const [key, def] of Object.entries(schema.properties)) {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
const value = getValue(data, fullKey);
|
||||
|
||||
html += `<div class="mb-4">
|
||||
<label class="block text-gray-700 text-sm font-bold mb-2">${def.title || key}</label>`;
|
||||
|
||||
if (def.type === 'object') {
|
||||
html += `<div class="pl-4 border-l-2 border-gray-200">
|
||||
${generateFormFields(def, data, fullKey, level)}
|
||||
</div>`;
|
||||
} else {
|
||||
html += generateInputField(def, fullKey, value, level);
|
||||
}
|
||||
|
||||
if (def.description) {
|
||||
html += `<p class="text-gray-500 text-xs mt-1">${def.description}</p>`;
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function generateInputField(def, key, value, level) {
|
||||
const baseClasses = "w-full p-2 border rounded bg-green-50"; // Agregado bg-green-50 para fondo verde claro
|
||||
|
||||
switch (def.type) {
|
||||
case 'string':
|
||||
if (def.enum) {
|
||||
return `<select class="${baseClasses}"
|
||||
onchange="updateConfig(${level}, '${key}', this.value)">
|
||||
${def.enum.map(opt => `<option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>`).join('')}
|
||||
</select>`;
|
||||
}
|
||||
return `<input type="text" value="${value || ''}"
|
||||
class="${baseClasses}"
|
||||
onchange="updateConfig(${level}, '${key}', this.value)">`;
|
||||
|
||||
case 'number':
|
||||
return `<input type="number" value="${value || 0}"
|
||||
class="${baseClasses}"
|
||||
onchange="updateConfig(${level}, '${key}', Number(this.value))">`;
|
||||
|
||||
case 'boolean':
|
||||
return `<div class="flex items-center">
|
||||
<input type="checkbox" ${value ? 'checked' : ''}
|
||||
class="form-checkbox h-5 w-5 bg-green-50"
|
||||
onchange="updateConfig(${level}, '${key}', this.checked)">
|
||||
</div>`;
|
||||
|
||||
default:
|
||||
return `<input type="text" value="${value || ''}"
|
||||
class="${baseClasses}"
|
||||
onchange="updateConfig(${level}, '${key}', this.value)">`;
|
||||
}
|
||||
}
|
||||
|
||||
async function modifySchema(level) {
|
||||
const response = await fetch(`/api/schema/${level}?group=${currentGroup}`);
|
||||
const schema = await response.json();
|
||||
|
||||
// Show schema editor modal
|
||||
const modal = document.getElementById('schema-editor');
|
||||
modal.classList.remove('hidden');
|
||||
|
||||
document.getElementById('schema-content').value = JSON.stringify(schema, null, 2);
|
||||
document.getElementById('schema-level').value = level;
|
||||
}
|
||||
|
||||
async function saveSchema() {
|
||||
const level = document.getElementById('schema-level').value;
|
||||
const content = document.getElementById('schema-content').value;
|
||||
|
||||
try {
|
||||
const schema = JSON.parse(content);
|
||||
await fetch(`/api/schema/${level}?group=${currentGroup}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(schema)
|
||||
});
|
||||
|
||||
// Refresh form
|
||||
const response = await fetch(`/api/config/${level}?group=${currentGroup}`);
|
||||
const data = await response.json();
|
||||
renderForm(`level${level}-form`, data);
|
||||
|
||||
// Hide modal
|
||||
document.getElementById('schema-editor').classList.add('hidden');
|
||||
} catch (e) {
|
||||
alert('Invalid JSON schema: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function getValue(data, path) {
|
||||
return path.split('.').reduce((obj, key) => obj?.[key], data);
|
||||
}
|
||||
|
||||
// Actualizar configuración cuando cambia un campo
|
||||
async function updateConfig(level, key, value) {
|
||||
const group = currentGroup;
|
||||
// Obtener configuración actual
|
||||
const response = await fetch(`/api/config/${level}?group=${group}`);
|
||||
const config = await response.json();
|
||||
|
||||
// Actualizar el valor usando la ruta del campo
|
||||
setNestedValue(config, key, value);
|
||||
|
||||
// Guardar configuración actualizada
|
||||
await fetch(`/api/config/${level}?group=${group}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
}
|
||||
|
||||
// Función auxiliar para establecer valor en objeto anidado
|
||||
function setNestedValue(obj, path, value) {
|
||||
const keys = path.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
if (!(keys[i] in current)) {
|
||||
current[keys[i]] = {};
|
||||
}
|
||||
current = current[keys[i]];
|
||||
}
|
||||
|
||||
current[keys[keys.length - 1]] = value;
|
||||
}
|
||||
|
||||
async function setWorkingDirectory() {
|
||||
if (!currentGroup) {
|
||||
alert('Por favor, seleccione un grupo de scripts primero');
|
||||
return;
|
||||
}
|
||||
|
||||
const path = document.getElementById('working-directory').value;
|
||||
await updateWorkingDirectory(path);
|
||||
}
|
||||
|
||||
async function initWorkingDirectory() {
|
||||
if (!currentGroup) return;
|
||||
|
||||
const response = await fetch(`/api/working-directory/${currentGroup}`);
|
||||
const result = await response.json();
|
||||
if (result.status === 'success' && result.path) {
|
||||
await updateWorkingDirectory(result.path);
|
||||
}
|
||||
}
|
||||
|
||||
async function browseDirectory() {
|
||||
console.log('Current group when browsing:', currentGroup); // Debug line
|
||||
if (!currentGroup) {
|
||||
alert('Por favor, seleccione un grupo de scripts primero');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPath = document.getElementById('working-directory').value;
|
||||
const response = await fetch(`/api/browse-directories?current_path=${encodeURIComponent(currentPath)}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
await updateWorkingDirectory(result.path);
|
||||
}
|
||||
}
|
||||
|
||||
// Nueva función auxiliar para actualizar el directorio de trabajo
|
||||
async function updateWorkingDirectory(path) {
|
||||
console.log('Updating working directory:', { path, group: currentGroup }); // Debug line
|
||||
|
||||
const response = await fetch('/api/working-directory', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
path: path,
|
||||
group: currentGroup
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log('Update result:', result); // Debug line
|
||||
|
||||
if (result.status === 'success') {
|
||||
// Actualizar input
|
||||
document.getElementById('working-directory').value = path;
|
||||
|
||||
// Recargar configuración de nivel 3
|
||||
const configResponse = await fetch(`/api/config/3?group=${currentGroup}`);
|
||||
const data = await configResponse.json();
|
||||
await renderForm('level3-form', data);
|
||||
} else {
|
||||
alert('Error: ' + (result.message || 'No se pudo actualizar el directorio de trabajo'));
|
||||
}
|
||||
}
|
||||
|
||||
// Función para alternar visibilidad de una sección
|
||||
function toggleConfig(sectionId) {
|
||||
const content = document.getElementById(sectionId);
|
||||
const button = document.querySelector(`[onclick="toggleConfig('${sectionId}')"]`);
|
||||
|
||||
if (content.classList.contains('hidden')) {
|
||||
content.classList.remove('hidden');
|
||||
button.innerText = 'Ocultar Configuración';
|
||||
|
||||
// Recargar la configuración al mostrar
|
||||
const level = sectionId.replace('level', '').replace('-content', '');
|
||||
const formId = `level${level}-form`;
|
||||
console.log(`Reloading config for level ${level}`); // Debug line
|
||||
|
||||
fetch(`/api/config/${level}?group=${currentGroup}`)
|
||||
.then(response => response.json())
|
||||
.then(data => renderForm(formId, data))
|
||||
.catch(error => console.error('Error reloading config:', error));
|
||||
} else {
|
||||
content.classList.add('hidden');
|
||||
button.innerText = 'Mostrar Configuración';
|
||||
}
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
document.getElementById('log-area').innerHTML = '';
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
async function initializeApp() {
|
||||
try {
|
||||
initWebSocket();
|
||||
|
||||
// Primero establecer el grupo actual
|
||||
const group = localStorage.getItem('selectedGroup');
|
||||
const selectElement = document.getElementById('script-group');
|
||||
if (group) {
|
||||
selectElement.value = group;
|
||||
}
|
||||
currentGroup = selectElement.value; // Siempre establecer currentGroup con el valor actual del select
|
||||
console.log('Current group initialized as:', currentGroup); // Debug line
|
||||
|
||||
// Luego cargar el directorio de trabajo
|
||||
await initWorkingDirectory();
|
||||
|
||||
// Finalmente cargar las configuraciones
|
||||
await loadConfigs();
|
||||
|
||||
// Configurar el evento de cambio de grupo
|
||||
selectElement.addEventListener('change', async (e) => {
|
||||
currentGroup = e.value;
|
||||
localStorage.setItem('selectedGroup', e.value);
|
||||
console.log('Group changed to:', currentGroup); // Debug line
|
||||
await initWorkingDirectory();
|
||||
await loadConfigs();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error during initialization:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Modificar la inicialización para usar la nueva función async
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeApp().catch(console.error);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Level 1 Configuration -->
|
||||
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Configuración General (Nivel 1)</h2>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onclick="toggleConfig('level1-content')">
|
||||
Mostrar Configuración
|
||||
</button>
|
||||
</div>
|
||||
<div id="level1-content" class="hidden">
|
||||
<div id="level1-form"></div>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded mt-4" onclick="modifySchema(1)">
|
||||
Modificar Esquema
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script Group Selection -->
|
||||
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-bold mb-4">Grupo de Scripts</h2>
|
||||
<select id="script-group" class="w-full p-2 border rounded">
|
||||
{% for group in script_groups %}
|
||||
<option value="{{ group }}">{{ group }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Level 2 Configuration -->
|
||||
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Configuración del Grupo (Nivel 2)</h2>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onclick="toggleConfig('level2-content')">
|
||||
Mostrar Configuración
|
||||
</button>
|
||||
</div>
|
||||
<div id="level2-content" class="hidden">
|
||||
<div id="level2-form"></div>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded mt-4" onclick="modifySchema(2)">
|
||||
Modificar Esquema
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Working Directory -->
|
||||
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-bold mb-4">Directorio de Trabajo</h2>
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-1 flex gap-2">
|
||||
<input type="text" id="working-directory" class="flex-1 p-2 border rounded bg-green-50">
|
||||
<button class="bg-gray-500 text-white px-4 py-2 rounded" onclick="browseDirectory()">
|
||||
Explorar
|
||||
</button>
|
||||
</div>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded" onclick="setWorkingDirectory()">
|
||||
Confirmar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Level 3 Configuration -->
|
||||
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Configuración del Directorio (Nivel 3)</h2>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
onclick="toggleConfig('level3-content')">
|
||||
Mostrar Configuración
|
||||
</button>
|
||||
</div>
|
||||
<div id="level3-content" class="hidden">
|
||||
<div id="level3-form"></div>
|
||||
<button class="bg-blue-500 text-white px-4 py-2 rounded mt-4" onclick="modifySchema(3)">
|
||||
Modificar Esquema
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts List -->
|
||||
<div class="mb-8 bg-white p-6 rounded-lg shadow">
|
||||
<h2 class="text-xl font-bold mb-4">Scripts Disponibles</h2>
|
||||
<div id="scripts-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Logs -->
|
||||
<div class="bg-white p-6 rounded-lg shadow">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Logs</h2>
|
||||
<button class="bg-red-500 text-white px-4 py-2 rounded" onclick="clearLogs()">
|
||||
Limpiar
|
||||
</button>
|
||||
</div>
|
||||
<div id="log-area" class="bg-gray-100 p-4 rounded h-64 overflow-y-auto font-mono text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schema Editor Modal -->
|
||||
<div id="schema-editor" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center">
|
||||
<div class="bg-white p-6 rounded-lg shadow-lg w-3/4 max-h-screen overflow-auto">
|
||||
<h3 class="text-xl font-bold mb-4">Editor de Esquema</h3>
|
||||
<textarea id="schema-content" class="w-full h-96 font-mono p-2 border rounded mb-4"></textarea>
|
||||
<input type="hidden" id="schema-level">
|
||||
<div class="flex justify-end gap-4">
|
||||
<button onclick="document.getElementById('schema-editor').classList.add('hidden')"
|
||||
class="bg-gray-500 text-white px-4 py-2 rounded">
|
||||
Cancelar
|
||||
</button>
|
||||
<button onclick="saveSchema()"
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded">
|
||||
Guardar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue