Se añadió una nueva ruta API para buscar archivos mediante un diálogo de selección, permitiendo al usuario elegir archivos desde su sistema. Además, se actualizaron los archivos de configuración JSON para incluir nuevos parámetros relacionados con el directorio de Obsidian y se mejoró la interfaz de usuario para manejar la selección de archivos, integrando un nuevo botón de búsqueda en los campos de entrada.

This commit is contained in:
Miguel 2025-07-14 10:55:08 +02:00
parent c8141deb63
commit c37e485fd3
5 changed files with 157 additions and 13 deletions

4
.cursor/rules/reglas.mdc Normal file
View File

@ -0,0 +1,4 @@
---
alwaysApply: true
---
Puedes usar .doc\MemoriaDeEvolucion.md para obtener un contexto de las ultimas modificaciones y conceptos sobre este proyecto. Quisiera que con los conocimientos importantes y desiciones importantes adquiridas en cada modificacion los agregues a MemoriaDeEvolucion.md manteniendo el estilo que ya tenemos de texto simple sin demasiado codigo y una semantica resumida.

View File

@ -0,0 +1,60 @@
# Memoria de Evolución - ParamManagerScripts
## Descripción Base del Proyecto
**ParamManagerScripts** es un sistema de gestión y ejecución de scripts que proporciona una interfaz web para organizar y ejecutar scripts de diferentes tipos (Python, C#, etc.) de manera ordenada y controlada.
### Funcionalidad Principal
#### 1. Sistema de Configuración Jerárquica
- **Nivel 1 (Global)**: Parámetros compartidos por todos los scripts (`data/data.json`)
- **Nivel 2 (Grupo)**: Configuración específica de cada grupo de scripts (`script_config.json`)
- **Nivel 3 (Trabajo)**: Parámetros específicos del directorio de trabajo (`work_dir.json`)
Esto esta definido en .doc\backend_setup.md
#### 2. Gestión de Scripts por Categorías
- **Scripts de Configuración**: Scripts Python tradicionales organizados en grupos
- **Launcher GUI**: Scripts Python con interfaz gráfica
- **Proyectos C#**: Ejecutables y proyectos .NET
- **Proyectos Python**: Scripts Python especializados
#### 3. Características del Frontend
- Interfaz web accesible via `http://127.0.0.1:5000/`
- Panel de logs en tiempo real con WebSocket
- Gestión de directorios de trabajo por grupo
- Integración con editores (VS Code, Cursor, Visual Studio)
- Icono de bandeja del sistema para acceso rápido
#### 4. Sistema de Launchers
- **Launcher General**: Para scripts Python con GUI
- **Launcher C#**: Gestión de proyectos y ejecutables .NET
- **Launcher Python**: Scripts Python especializados con gestión de entornos
#### 5. Servicios Compartidos
- **ExcelService**: Manipulación de archivos Excel
- **LanguageService**: Detección de idiomas
- **LLMService**: Integración con modelos de IA (OpenAI, Groq, Claude, etc.)
Esto esta definido en .doc\backend_setup.md
### Arquitectura Técnica
- **Backend**: Flask con WebSocket para comunicación en tiempo real
- **Frontend**: HTML/CSS/JavaScript con comunicación AJAX
- **Configuración**: Sistema de archivos JSON con validación por esquemas
- **Ejecución**: Subprocesos controlados con logging centralizado
- **Codificación**: UTF-8 obligatorio para evitar problemas de caracteres
### Concepto Clave: Orientación a Directorios de Trabajo
El sistema está diseñado para trabajar con directorios específicos donde los scripts procesan archivos. Cada grupo puede tener su directorio de trabajo configurado, y los scripts acceden a la configuración consolidada a través de `load_configuration()` que combina los tres niveles de configuración.
---
## Historial de Cambios
### [Fecha de Creación] - Descripción Base
- Documentación inicial del sistema ParamManagerScripts
- Descripción de la arquitectura y funcionalidades principales
- Establecimiento de la estructura de memoria de evolución

34
app.py
View File

@ -244,6 +244,40 @@ def browse_directories():
return jsonify({"status": "cancelled"}) return jsonify({"status": "cancelled"})
@app.route("/api/browse-files")
def browse_files():
import tkinter as tk
from tkinter import filedialog
# Obtener el path inicial
current_path = request.args.get("current_path")
initial_dir = os.path.dirname(os.path.abspath(__file__)) # Default
if current_path:
if os.path.isdir(current_path):
initial_dir = current_path
elif os.path.isfile(current_path):
initial_dir = os.path.dirname(current_path)
# 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 archivo
filepath = filedialog.askopenfilename(
initialdir=initial_dir,
title="Seleccionar Archivo",
filetypes=(("All files", "*.*"),),
)
# Destruir la ventana de tkinter
root.destroy()
if filepath:
return jsonify({"status": "success", "path": filepath})
return jsonify({"status": "cancelled"})
@app.route("/api/logs", methods=["GET", "DELETE"]) @app.route("/api/logs", methods=["GET", "DELETE"])
def handle_logs(): def handle_logs():
if request.method == "GET": if request.method == "GET":

View File

@ -1,6 +1,19 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"ObsideanDir": {
"type": "string",
"title": "Directorio de Vault de Obsidean",
"description": "Directorio de Vault de Obsidean",
"format": "directory",
"default": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM"
},
"ObsideanProjectsBase": {
"type": "string",
"title": "Subdirectorio",
"description": "Subdirectorio de los proyectos actuales en el Vault de Obsidean",
"default": "\\04-SIDEL"
},
"siemens_exp_directory": { "siemens_exp_directory": {
"type": "string", "type": "string",
"title": "Directorio base donde esta la exportación de el proyecto de Tia", "title": "Directorio base donde esta la exportación de el proyecto de Tia",
@ -19,18 +32,12 @@
"description": "", "description": "",
"format": "directory" "format": "directory"
}, },
"ObsideanDir": { "io_excel_file_from_ediagram": {
"type": "string", "type": "string",
"title": "Directorio de Vault de Obsidean", "title": "IO scanned from ED",
"description": "Directorio de Vault de Obsidean", "description": "IO scanned from Electrica Diagram",
"format": "directory", "format": "file",
"default": "C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM" "default": "IO.xlsx"
},
"ObsideanProjectsBase": {
"type": "string",
"title": "Subdirectorio",
"description": "Subdirectorio de los proyectos actuales en el Vault de Obsidean",
"default": "\\04-SIDEL"
} }
} }
} }

View File

@ -396,6 +396,18 @@ function generateInputField(def, key, value, level) {
</button> </button>
</div>`; </div>`;
} }
if (def.format === 'file') {
return `<div class="flex gap-2">
<input type="text" value="${value || ''}"
class="${baseClasses} flex-grow" data-key="${key}">
<button type="button"
onclick="browseFieldFile(this)"
class="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600"
data-key="${key}">
Buscar...
</button>
</div>`;
}
if (def.enum) { if (def.enum) {
return `<select class="${baseClasses}" data-key="${key}"> return `<select class="${baseClasses}" data-key="${key}">
${def.enum.map(opt => `<option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>`).join('')} ${def.enum.map(opt => `<option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>`).join('')}
@ -439,6 +451,29 @@ async function browseFieldDirectory(button) {
} }
} }
// Agregar nueva función para manejar la búsqueda de un archivo
async function browseFieldFile(button) {
const input = button.parentElement.querySelector('input');
const currentPath = input.value;
try {
const response = await fetch(`/api/browse-files?current_path=${encodeURIComponent(currentPath)}`);
const result = await response.json();
if (result.status === 'success') {
input.value = result.path;
// Disparar un evento change para actualizar el valor internamente
const event = new Event('change', {
bubbles: true
});
input.dispatchEvent(event);
}
} catch (error) {
console.error('Error browsing file:', error);
alert('Error al buscar el archivo');
}
}
async function modifySchema(level) { async function modifySchema(level) {
try { try {
console.log('Loading schema for level:', level); // Debug line console.log('Loading schema for level:', level); // Debug line
@ -546,6 +581,7 @@ function createFieldEditor(key, field) {
onchange="updateFieldType(this)"> onchange="updateFieldType(this)">
<option value="string" ${field.type === 'string' && !field.enum && !field.format ? 'selected' : ''}>Texto</option> <option value="string" ${field.type === 'string' && !field.enum && !field.format ? 'selected' : ''}>Texto</option>
<option value="directory" ${field.type === 'string' && field.format === 'directory' ? 'selected' : ''}>Directorio</option> <option value="directory" ${field.type === 'string' && field.format === 'directory' ? 'selected' : ''}>Directorio</option>
<option value="file" ${field.type === 'string' && field.format === 'file' ? 'selected' : ''}>Archivo</option>
<option value="number" ${field.type === 'number' ? 'selected' : ''}>Número</option> <option value="number" ${field.type === 'number' ? 'selected' : ''}>Número</option>
<option value="boolean" ${field.type === 'boolean' ? 'selected' : ''}>Booleano</option> <option value="boolean" ${field.type === 'boolean' ? 'selected' : ''}>Booleano</option>
<option value="enum" ${field.enum ? 'selected' : ''}>Lista de Opciones</option> <option value="enum" ${field.enum ? 'selected' : ''}>Lista de Opciones</option>
@ -652,16 +688,19 @@ function updateVisualSchema() {
const defaultValueString = defaultValueInput.value; const defaultValueString = defaultValueInput.value;
let propertyDefinition = { let propertyDefinition = {
type: fieldType === 'directory' || fieldType === 'enum' ? 'string' : fieldType, // El tipo base type: fieldType === 'directory' || fieldType === 'enum' || fieldType === 'file' ? 'string' : fieldType, // El tipo base
title: title, title: title,
description: description description: description
}; };
// Añadir formato específico si es directorio // Añadir formato específico si es directorio o archivo
if (select.value === 'directory') { if (select.value === 'directory') {
propertyDefinition.format = 'directory'; propertyDefinition.format = 'directory';
} else if (select.value === 'file') {
propertyDefinition.format = 'file';
} }
// Añadir enum si es de tipo enum // Añadir enum si es de tipo enum
if (select.value === 'enum') { if (select.value === 'enum') {
propertyDefinition.enum = field.querySelector('textarea').value.split('\n').filter(v => v.trim()); propertyDefinition.enum = field.querySelector('textarea').value.split('\n').filter(v => v.trim());