diff --git a/__pycache__/config_manager.cpython-310.pyc b/__pycache__/config_manager.cpython-310.pyc index 6da376a..88e9b57 100644 Binary files a/__pycache__/config_manager.cpython-310.pyc and b/__pycache__/config_manager.cpython-310.pyc differ diff --git a/backend/script_groups/esquema.json b/backend/script_groups/esquema.json index 16cbb07..54d842d 100644 --- a/backend/script_groups/esquema.json +++ b/backend/script_groups/esquema.json @@ -1,20 +1,20 @@ { + "type": "object", "properties": { "batch_size": { - "description": "N\u00c3\u00bamero de elementos a procesar por lote", + "type": "number", "title": "Tama\u00c3\u00b1o de Lote", - "type": "number" + "description": "N\u00c3\u00bamero de elementos a procesar por lote" }, "input_dir": { - "description": "Ruta al directorio de archivos de entrada", + "type": "string", "title": "Directorio de Entrada", - "type": "string" + "description": "Ruta al directorio de archivos de entrada" }, "output_dir": { - "description": "Ruta al directorio para archivos generados", + "type": "string", "title": "Directorio de Salida", - "type": "string" + "description": "Ruta al directorio para archivos generados" } - }, - "type": "object" + } } \ No newline at end of file diff --git a/backend/script_groups/example_group/description.json b/backend/script_groups/example_group/description.json new file mode 100644 index 0000000..0e0f8c1 --- /dev/null +++ b/backend/script_groups/example_group/description.json @@ -0,0 +1,6 @@ +{ + "name": "Grupo de Ejemplo", + "description": "Scripts de demostración que muestran las funcionalidades básicas del sistema", + "version": "1.0", + "author": "Admin" +} diff --git a/backend/script_groups/example_group/schema.json b/backend/script_groups/example_group/schema.json new file mode 100644 index 0000000..1c9e43a --- /dev/null +++ b/backend/script_groups/example_group/schema.json @@ -0,0 +1,4 @@ +{ + "type": "object", + "properties": {} +} \ No newline at end of file diff --git a/config_manager.py b/config_manager.py index 542ad50..8a943de 100644 --- a/config_manager.py +++ b/config_manager.py @@ -66,13 +66,36 @@ class ConfigurationManager: 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_script_groups(self) -> List[Dict[str, Any]]: + """Returns list of available script groups with their descriptions.""" + groups = [] + for d in os.listdir(self.script_groups_path): + group_path = os.path.join(self.script_groups_path, d) + if os.path.isdir(group_path): + description = self._get_group_description(group_path) + groups.append( + { + "id": d, + "name": description.get("name", d), + "description": description.get( + "description", "Sin descripción" + ), + "version": description.get("version", "1.0"), + "author": description.get("author", "Unknown"), + } + ) + return groups + + def _get_group_description(self, group_path: str) -> Dict[str, Any]: + """Get description for a script group.""" + description_file = os.path.join(group_path, "description.json") + try: + if os.path.exists(description_file): + with open(description_file, "r", encoding="utf-8") as f: + return json.load(f) + except Exception as e: + print(f"Error reading group description: {e}") + return {} def get_config(self, level: str, group: str = None) -> Dict[str, Any]: """Get configuration for specified level.""" @@ -93,27 +116,159 @@ class ConfigurationManager: 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: + # Clean level parameter + level = str(level).split("-")[0] + + # Determine schema path based on level if level == "1": path = os.path.join(self.data_path, "esquema.json") + # Try esquema.json first, then schema.json if not found + if not os.path.exists(path): + path = os.path.join(self.data_path, "schema.json") elif level == "2": - path = os.path.join(self.script_groups_path, "esquema.json") + path = os.path.join(self.script_groups_path, group, "esquema.json") + # Try esquema.json first, then schema.json if not found + if not os.path.exists(path): + path = os.path.join(self.script_groups_path, group, "schema.json") elif level == "3": if not group: - return {} # Return empty schema if no group is specified + return {"type": "object", "properties": {}} path = os.path.join(self.script_groups_path, group, "esquema.json") + # Try esquema.json first, then schema.json if not found + if not os.path.exists(path): + path = os.path.join(self.script_groups_path, group, "schema.json") else: - return {} # Return empty schema for invalid levels + return {"type": "object", "properties": {}} - 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 + # Read existing schema from whichever file exists + if os.path.exists(path): + with open(path, "r", encoding="utf-8") as f: + schema = json.load(f) + return ( + schema + if isinstance(schema, dict) + else {"type": "object", "properties": {}} + ) + + # Create default schema if no file exists + default_schema = {"type": "object", "properties": {}} + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w", encoding="utf-8") as f: + json.dump(default_schema, f, indent=2) + return default_schema + + except Exception as e: + print(f"Error loading schema: {str(e)}") + return {"type": "object", "properties": {}} + + def update_schema( + self, level: str, data: Dict[str, Any], group: str = None + ) -> Dict[str, str]: + """Update schema for specified level and clean corresponding config.""" + try: + # Determinar rutas de schema y config + if level == "1": + schema_path = os.path.join(self.data_path, "esquema.json") + config_path = os.path.join(self.data_path, "data.json") + elif level == "2": + schema_path = os.path.join( + self.script_groups_path, group, "esquema.json" + ) + config_path = os.path.join(self.script_groups_path, group, "data.json") + elif level == "3": + if not group: + return { + "status": "error", + "message": "Group is required for level 3", + } + schema_path = os.path.join( + self.script_groups_path, group, "esquema.json" + ) + config_path = ( + os.path.join(self.working_directory, "data.json") + if self.working_directory + else None + ) + else: + return {"status": "error", "message": "Invalid level"} + + # Ensure directory exists + os.makedirs(os.path.dirname(schema_path), exist_ok=True) + + # Validate schema structure + if ( + not isinstance(data, dict) + or "type" not in data + or "properties" not in data + ): + data = { + "type": "object", + "properties": data if isinstance(data, dict) else {}, + } + + # Write schema + with open(schema_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + # Clean corresponding config file + self._clean_config_for_schema(config_path, data) + + return {"status": "success"} + + except Exception as e: + print(f"Error updating schema: {str(e)}") + return {"status": "error", "message": str(e)} + + def _clean_config_for_schema( + self, config_path: str, schema: Dict[str, Any] + ) -> None: + """Clean configuration file to match schema structure.""" + if not config_path or not os.path.exists(config_path): + return + + try: + # Cargar configuración actual + with open(config_path, "r", encoding="utf-8") as f: + config = json.load(f) + + # Limpiar configuración recursivamente + cleaned_config = self._clean_object_against_schema(config, schema) + + # Guardar configuración limpia + with open(config_path, "w", encoding="utf-8") as f: + json.dump(cleaned_config, f, indent=2, ensure_ascii=False) + + except Exception as e: + print(f"Error cleaning config: {str(e)}") + + def _clean_object_against_schema( + self, data: Dict[str, Any], schema: Dict[str, Any] + ) -> Dict[str, Any]: + """Recursively clean object to match schema structure.""" + if not isinstance(data, dict) or not isinstance(schema, dict): + return {} + + result = {} + schema_props = schema.get("properties", {}) + + for key, value in data.items(): + # Solo mantener campos que existen en el schema + if key in schema_props: + prop_schema = schema_props[key] + + # Si es un objeto anidado, limpiar recursivamente + if prop_schema.get("type") == "object": + result[key] = self._clean_object_against_schema(value, prop_schema) + # Si es un enum, verificar que el valor sea válido + elif "enum" in prop_schema: + if value in prop_schema["enum"]: + result[key] = value + # Para otros tipos, mantener el valor + else: + result[key] = value + + return result def update_config( self, level: str, data: Dict[str, Any], group: str = None @@ -132,20 +287,6 @@ class ConfigurationManager: 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: diff --git a/data/data.json b/data/data.json index e599297..245a1ab 100644 --- a/data/data.json +++ b/data/data.json @@ -1,6 +1,4 @@ { "api_key": "your-api-key-here", - "model": "gpt-3.5-turbo", - "max_tokens": 1000, - "temperature": 0.7 -} + "model": "gpt-3.5-turbo" +} \ No newline at end of file diff --git a/data/esquema.json b/data/esquema.json index 88b52aa..5d04917 100644 --- a/data/esquema.json +++ b/data/esquema.json @@ -1,32 +1,20 @@ { + "type": "object", "properties": { "api_key": { - "description": "Tu clave de API para servicios externos", + "type": "string", "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" + "description": "Tu clave de API para servicios externos" }, "model": { + "type": "string", + "title": "Modelo LLM", "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" + } } \ No newline at end of file diff --git a/data/log.txt b/data/log.txt index cac062f..7119ef2 100644 --- a/data/log.txt +++ b/data/log.txt @@ -5,9 +5,7 @@ Iniciando ejecución de x1.py... Configuraciones cargadas: Nivel 1: { "api_key": "your-api-key-here", - "model": "gpt-3.5-turbo", - "max_tokens": 1000, - "temperature": 0.7 + "model": "gpt-3.5-turbo" } Nivel 2: { "input_dir": "D:/Datos/Entrada", diff --git a/data/schema.json b/data/schema.json new file mode 100644 index 0000000..1c9e43a --- /dev/null +++ b/data/schema.json @@ -0,0 +1,4 @@ +{ + "type": "object", + "properties": {} +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 81639c4..d8dd941 100644 --- a/templates/index.html +++ b/templates/index.html @@ -92,7 +92,7 @@ // Form rendering functionality async function renderForm(containerId, data) { - console.log(`Rendering form for ${containerId}`); // Debug line + console.log(`Rendering form for ${containerId} with data:`, data); // Debug line const container = document.getElementById(containerId); const level = containerId.replace('level', '').split('-')[0]; @@ -101,13 +101,14 @@ const schema = await schemaResponse.json(); console.log(`Schema for level ${level}:`, schema); // Debug line - if (Object.keys(schema).length === 0) { + if (!schema || !schema.properties || Object.keys(schema.properties).length === 0) { container.innerHTML = '

No hay esquema definido para este nivel.

'; - } else { - const formHtml = generateFormFields(schema, data, '', level); - console.log(`Generated HTML for ${containerId}:`, formHtml.substring(0, 100) + '...'); // Debug line - container.innerHTML = formHtml; + return; } + + 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 = '

Error cargando el esquema.

'; @@ -115,10 +116,18 @@ } function generateFormFields(schema, data, prefix, level) { + console.log('Generating fields with data:', { schema, data, prefix, level }); // Debug line let html = ''; + + if (!schema.properties) { + console.warn('Schema has no properties'); + return html; + } + for (const [key, def] of Object.entries(schema.properties)) { const fullKey = prefix ? `${prefix}.${key}` : key; const value = getValue(data, fullKey); + console.log(`Field ${fullKey}:`, { definition: def, value: value }); // Debug line html += `
`; @@ -139,6 +148,15 @@ return html; } + function getValue(data, path) { + console.log('Getting value for path:', { path, data }); // Debug line + if (!data || !path) return undefined; + + const value = path.split('.').reduce((obj, key) => obj?.[key], data); + console.log('Found value:', value); // Debug line + return value; + } + 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 @@ -177,33 +195,45 @@ try { console.log('Loading schema for level:', level); // Debug line const response = await fetch(`/api/schema/${level}?group=${currentGroup}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } const schema = await response.json(); console.log('Loaded schema:', schema); // Debug line // Show schema editor modal const modal = document.getElementById('schema-editor'); + if (!modal) { + throw new Error('Schema editor modal not found'); + } modal.classList.remove('hidden'); - // Inicializar JSON editor - const jsonEditor = document.getElementById('json-editor'); - jsonEditor.value = JSON.stringify(schema, null, 2); + // Inicializar el esquema si está vacío + const finalSchema = Object.keys(schema).length === 0 ? + { type: 'object', properties: {} } : schema; - // Inicializar visual editor + // Inicializar editores + const jsonEditor = document.getElementById('json-editor'); const visualEditor = document.getElementById('visual-editor'); + const schemaLevel = document.getElementById('schema-level'); + + if (!jsonEditor || !visualEditor || !schemaLevel) { + throw new Error('Required editor elements not found'); + } + + jsonEditor.value = JSON.stringify(finalSchema, null, 2); visualEditor.innerHTML = '
' + ''; + schemaLevel.value = level; - // Renderizar campos existentes - renderVisualEditor(schema); - - // Guardar nivel actual - document.getElementById('schema-level').value = level; + // Renderizar editor visual + renderVisualEditor(finalSchema); // Activar pestaña visual por defecto switchEditorMode('visual'); } catch (error) { console.error('Error loading schema:', error); - alert('Error cargando el esquema'); + alert('Error cargando el esquema: ' + error.message); } } @@ -218,11 +248,27 @@ jsonEditor.classList.add('hidden'); visualTab.classList.add('border-blue-500'); jsonTab.classList.remove('border-blue-500'); + + // Actualizar el editor visual desde JSON + try { + const schema = JSON.parse(jsonEditor.value); + renderVisualEditor(schema); + } catch (e) { + console.error('Error parsing JSON:', e); + } } else { visualEditor.classList.add('hidden'); jsonEditor.classList.remove('hidden'); visualTab.classList.remove('border-blue-500'); jsonTab.classList.add('border-blue-500'); + + // Actualizar el JSON desde el editor visual + try { + const schema = updateVisualSchema(); + jsonEditor.value = JSON.stringify(schema, null, 2); + } catch (e) { + console.error('Error updating JSON:', e); + } } } @@ -333,91 +379,85 @@ // Funciones de actualización del esquema visual function updateVisualSchema() { - const fields = document.getElementById('schema-fields').children; - const schema = { - type: 'object', - properties: {} - }; - - Array.from(fields).forEach(field => { - const inputs = field.getElementsByTagName('input'); - const select = field.getElementsByTagName('select')[0]; - const key = inputs[0].value; - - schema.properties[key] = { - type: select.value === 'enum' ? 'string' : select.value, - title: inputs[1].value, - description: inputs[2].value + try { + const fields = document.getElementById('schema-fields').children; + const schema = { + type: 'object', + properties: {} }; - if (select.value === 'enum') { - const textarea = field.getElementsByTagName('textarea')[0]; - schema.properties[key].enum = textarea.value.split('\n').filter(v => v.trim()); - } - }); + Array.from(fields).forEach(field => { + const inputs = field.getElementsByTagName('input'); + const select = field.getElementsByTagName('select')[0]; + const key = inputs[0].value; + + schema.properties[key] = { + type: select.value === 'enum' ? 'string' : select.value, + title: inputs[1].value, + description: inputs[2].value + }; - document.getElementById('schema-content').value = JSON.stringify(schema, null, 2); - return schema; + if (select.value === 'enum') { + const textarea = field.getElementsByTagName('textarea')[0]; + if (textarea) { + schema.properties[key].enum = textarea.value.split('\n').filter(v => v.trim()); + } + } + }); + + // Actualizar el JSON editor directamente + const jsonEditor = document.getElementById('json-editor'); + if (jsonEditor) { + jsonEditor.value = JSON.stringify(schema, null, 2); + } + + return schema; + } catch (error) { + console.error('Error updating schema:', error); + return null; + } } async function saveSchema() { try { const level = document.getElementById('schema-level').value; - const schema = updateVisualSchema(); + let schema; - await fetch(`/api/schema/${level}?group=${currentGroup}`, { + // Obtener el esquema según el modo activo + const visualEditor = document.getElementById('visual-editor'); + const jsonEditor = document.getElementById('json-editor'); + + if (!visualEditor.classList.contains('hidden')) { + schema = updateVisualSchema(); + } else { + schema = JSON.parse(jsonEditor.value); + } + + console.log('Saving schema:', schema); // Debug line + + const response = await fetch(`/api/schema/${level}?group=${currentGroup}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(schema) }); - const response = await fetch(`/api/config/${level}?group=${currentGroup}`); - const data = await response.json(); - renderForm(`level${level}-form`, data); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + // Recargar el formulario + const configResponse = await fetch(`/api/config/${level}?group=${currentGroup}`); + const data = await configResponse.json(); + await renderForm(`level${level}-form`, data); + + // Cerrar modal document.getElementById('schema-editor').classList.add('hidden'); } catch (e) { + console.error('Error saving schema:', e); alert('Error guardando esquema: ' + 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'); @@ -537,6 +577,17 @@ } currentGroup = selectElement.value; // Siempre establecer currentGroup con el valor actual del select console.log('Current group initialized as:', currentGroup); // Debug line + updateGroupDescription(); // Actualizar descripción inicial + + // Configurar el evento de cambio de grupo + selectElement.addEventListener('change', async (e) => { + currentGroup = e.target.value; + localStorage.setItem('selectedGroup', e.target.value); + console.log('Group changed to:', currentGroup); // Debug line + updateGroupDescription(); // Actualizar descripción al cambiar + await initWorkingDirectory(); + await loadConfigs(); + }); // Luego cargar el directorio de trabajo await initWorkingDirectory(); @@ -582,6 +633,13 @@ logArea.innerHTML += lines; logArea.scrollTop = logArea.scrollHeight; } + + function updateGroupDescription() { + const select = document.getElementById('script-group'); + const option = select.options[select.selectedIndex]; + const description = option.getAttribute('data-description'); + document.getElementById('group-description').textContent = description; + } @@ -606,11 +664,16 @@

Grupo de Scripts

- {% for group in script_groups %} - + {% endfor %} +

+
+ + +