Funcionando con la edicion de los esquemas

This commit is contained in:
Miguel 2025-02-09 11:09:21 +01:00
parent 6b72069c51
commit 64a8b41881
10 changed files with 351 additions and 149 deletions

View File

@ -1,20 +1,20 @@
{ {
"type": "object",
"properties": { "properties": {
"batch_size": { "batch_size": {
"description": "N\u00c3\u00bamero de elementos a procesar por lote", "type": "number",
"title": "Tama\u00c3\u00b1o de Lote", "title": "Tama\u00c3\u00b1o de Lote",
"type": "number" "description": "N\u00c3\u00bamero de elementos a procesar por lote"
}, },
"input_dir": { "input_dir": {
"description": "Ruta al directorio de archivos de entrada", "type": "string",
"title": "Directorio de Entrada", "title": "Directorio de Entrada",
"type": "string" "description": "Ruta al directorio de archivos de entrada"
}, },
"output_dir": { "output_dir": {
"description": "Ruta al directorio para archivos generados", "type": "string",
"title": "Directorio de Salida", "title": "Directorio de Salida",
"type": "string" "description": "Ruta al directorio para archivos generados"
} }
}, }
"type": "object"
} }

View File

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

View File

@ -0,0 +1,4 @@
{
"type": "object",
"properties": {}
}

View File

@ -66,13 +66,36 @@ class ConfigurationManager:
return {"status": "success", "path": path} return {"status": "success", "path": path}
def get_script_groups(self) -> List[str]: def get_script_groups(self) -> List[Dict[str, Any]]:
"""Returns list of available script groups.""" """Returns list of available script groups with their descriptions."""
return [ groups = []
d for d in os.listdir(self.script_groups_path):
for d in os.listdir(self.script_groups_path) group_path = os.path.join(self.script_groups_path, d)
if os.path.isdir(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]: def get_config(self, level: str, group: str = None) -> Dict[str, Any]:
"""Get configuration for specified level.""" """Get configuration for specified level."""
@ -93,27 +116,159 @@ class ConfigurationManager:
def get_schema(self, level: str, group: str = None) -> Dict[str, Any]: def get_schema(self, level: str, group: str = None) -> Dict[str, Any]:
"""Get schema for specified level.""" """Get schema for specified level."""
# Clean level parameter (remove -form suffix if present)
level = level.split("-")[0]
try: try:
# Clean level parameter
level = str(level).split("-")[0]
# Determine schema path based on level
if level == "1": if level == "1":
path = os.path.join(self.data_path, "esquema.json") 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": 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": elif level == "3":
if not group: 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") 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: else:
return {} # Return empty schema for invalid levels return {"type": "object", "properties": {}}
with open(path, "r") as f: # Read existing schema from whichever file exists
return json.load(f) if os.path.exists(path):
except FileNotFoundError: with open(path, "r", encoding="utf-8") as f:
return {} # Return empty schema if file doesn't exist schema = json.load(f)
except json.JSONDecodeError: return (
return {} # Return empty schema if file is invalid JSON 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( def update_config(
self, level: str, data: Dict[str, Any], group: str = None self, level: str, data: Dict[str, Any], group: str = None
@ -132,20 +287,6 @@ class ConfigurationManager:
with open(path, "w") as f: with open(path, "w") as f:
json.dump(data, f, indent=2) 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]]: def list_scripts(self, group: str) -> List[Dict[str, str]]:
"""List all scripts in a group with their descriptions.""" """List all scripts in a group with their descriptions."""
try: try:

View File

@ -1,6 +1,4 @@
{ {
"api_key": "your-api-key-here", "api_key": "your-api-key-here",
"model": "gpt-3.5-turbo", "model": "gpt-3.5-turbo"
"max_tokens": 1000,
"temperature": 0.7
} }

View File

@ -1,32 +1,20 @@
{ {
"type": "object",
"properties": { "properties": {
"api_key": { "api_key": {
"description": "Tu clave de API para servicios externos", "type": "string",
"title": "API Key", "title": "API Key",
"type": "string" "description": "Tu clave de API para servicios externos"
},
"max_tokens": {
"description": "N\u00c3\u00bamero m\u00c3\u00a1ximo de tokens por respuesta",
"title": "Tokens M\u00c3\u00a1ximos",
"type": "number"
}, },
"model": { "model": {
"type": "string",
"title": "Modelo LLM",
"description": "Modelo de lenguaje a utilizar", "description": "Modelo de lenguaje a utilizar",
"enum": [ "enum": [
"gpt-3.5-turbo", "gpt-3.5-turbo",
"gpt-4", "gpt-4",
"claude-v1" "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"
} }

View File

@ -5,9 +5,7 @@ Iniciando ejecución de x1.py...
Configuraciones cargadas: Configuraciones cargadas:
Nivel 1: { Nivel 1: {
"api_key": "your-api-key-here", "api_key": "your-api-key-here",
"model": "gpt-3.5-turbo", "model": "gpt-3.5-turbo"
"max_tokens": 1000,
"temperature": 0.7
} }
Nivel 2: { Nivel 2: {
"input_dir": "D:/Datos/Entrada", "input_dir": "D:/Datos/Entrada",

4
data/schema.json Normal file
View File

@ -0,0 +1,4 @@
{
"type": "object",
"properties": {}
}

View File

@ -92,7 +92,7 @@
// Form rendering functionality // Form rendering functionality
async function renderForm(containerId, data) { 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 container = document.getElementById(containerId);
const level = containerId.replace('level', '').split('-')[0]; const level = containerId.replace('level', '').split('-')[0];
@ -101,13 +101,14 @@
const schema = await schemaResponse.json(); const schema = await schemaResponse.json();
console.log(`Schema for level ${level}:`, schema); // Debug line 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 = '<p class="text-gray-500">No hay esquema definido para este nivel.</p>'; container.innerHTML = '<p class="text-gray-500">No hay esquema definido para este nivel.</p>';
} else { return;
const formHtml = generateFormFields(schema, data, '', level);
console.log(`Generated HTML for ${containerId}:`, formHtml.substring(0, 100) + '...'); // Debug line
container.innerHTML = formHtml;
} }
const formHtml = generateFormFields(schema, data || {}, '', level);
console.log(`Generated HTML for ${containerId}:`, formHtml.substring(0, 100) + '...'); // Debug line
container.innerHTML = formHtml;
} catch (error) { } catch (error) {
console.error(`Error rendering form ${containerId}:`, error); console.error(`Error rendering form ${containerId}:`, error);
container.innerHTML = '<p class="text-red-500">Error cargando el esquema.</p>'; container.innerHTML = '<p class="text-red-500">Error cargando el esquema.</p>';
@ -115,10 +116,18 @@
} }
function generateFormFields(schema, data, prefix, level) { function generateFormFields(schema, data, prefix, level) {
console.log('Generating fields with data:', { schema, data, prefix, level }); // Debug line
let html = ''; let html = '';
if (!schema.properties) {
console.warn('Schema has no properties');
return html;
}
for (const [key, def] of Object.entries(schema.properties)) { for (const [key, def] of Object.entries(schema.properties)) {
const fullKey = prefix ? `${prefix}.${key}` : key; const fullKey = prefix ? `${prefix}.${key}` : key;
const value = getValue(data, fullKey); const value = getValue(data, fullKey);
console.log(`Field ${fullKey}:`, { definition: def, value: value }); // Debug line
html += `<div class="mb-4"> html += `<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">${def.title || key}</label>`; <label class="block text-gray-700 text-sm font-bold mb-2">${def.title || key}</label>`;
@ -139,6 +148,15 @@
return html; 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) { 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 const baseClasses = "w-full p-2 border rounded bg-green-50"; // Agregado bg-green-50 para fondo verde claro
@ -177,33 +195,45 @@
try { try {
console.log('Loading schema for level:', level); // Debug line console.log('Loading schema for level:', level); // Debug line
const response = await fetch(`/api/schema/${level}?group=${currentGroup}`); 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(); const schema = await response.json();
console.log('Loaded schema:', schema); // Debug line console.log('Loaded schema:', schema); // Debug line
// Show schema editor modal // Show schema editor modal
const modal = document.getElementById('schema-editor'); const modal = document.getElementById('schema-editor');
if (!modal) {
throw new Error('Schema editor modal not found');
}
modal.classList.remove('hidden'); modal.classList.remove('hidden');
// Inicializar JSON editor // Inicializar el esquema si está vacío
const jsonEditor = document.getElementById('json-editor'); const finalSchema = Object.keys(schema).length === 0 ?
jsonEditor.value = JSON.stringify(schema, null, 2); { type: 'object', properties: {} } : schema;
// Inicializar visual editor // Inicializar editores
const jsonEditor = document.getElementById('json-editor');
const visualEditor = document.getElementById('visual-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 = '<div id="schema-fields" class="mb-4"></div>' + visualEditor.innerHTML = '<div id="schema-fields" class="mb-4"></div>' +
'<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">Agregar Campo</button>'; '<button onclick="addSchemaField()" class="mt-4 bg-green-500 text-white px-4 py-2 rounded">Agregar Campo</button>';
schemaLevel.value = level;
// Renderizar campos existentes // Renderizar editor visual
renderVisualEditor(schema); renderVisualEditor(finalSchema);
// Guardar nivel actual
document.getElementById('schema-level').value = level;
// Activar pestaña visual por defecto // Activar pestaña visual por defecto
switchEditorMode('visual'); switchEditorMode('visual');
} catch (error) { } catch (error) {
console.error('Error loading schema:', 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'); jsonEditor.classList.add('hidden');
visualTab.classList.add('border-blue-500'); visualTab.classList.add('border-blue-500');
jsonTab.classList.remove('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 { } else {
visualEditor.classList.add('hidden'); visualEditor.classList.add('hidden');
jsonEditor.classList.remove('hidden'); jsonEditor.classList.remove('hidden');
visualTab.classList.remove('border-blue-500'); visualTab.classList.remove('border-blue-500');
jsonTab.classList.add('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 // Funciones de actualización del esquema visual
function updateVisualSchema() { function updateVisualSchema() {
const fields = document.getElementById('schema-fields').children; try {
const schema = { const fields = document.getElementById('schema-fields').children;
type: 'object', const schema = {
properties: {} 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
}; };
if (select.value === 'enum') { Array.from(fields).forEach(field => {
const textarea = field.getElementsByTagName('textarea')[0]; const inputs = field.getElementsByTagName('input');
schema.properties[key].enum = textarea.value.split('\n').filter(v => v.trim()); const select = field.getElementsByTagName('select')[0];
} const key = inputs[0].value;
});
document.getElementById('schema-content').value = JSON.stringify(schema, null, 2); schema.properties[key] = {
return schema; type: select.value === 'enum' ? 'string' : select.value,
title: inputs[1].value,
description: inputs[2].value
};
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() { async function saveSchema() {
try { try {
const level = document.getElementById('schema-level').value; 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', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(schema) body: JSON.stringify(schema)
}); });
const response = await fetch(`/api/config/${level}?group=${currentGroup}`); if (!response.ok) {
const data = await response.json(); throw new Error(`HTTP error! status: ${response.status}`);
renderForm(`level${level}-form`, data); }
// 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'); document.getElementById('schema-editor').classList.add('hidden');
} catch (e) { } catch (e) {
console.error('Error saving schema:', e);
alert('Error guardando esquema: ' + e.message); 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() { async function setWorkingDirectory() {
if (!currentGroup) { if (!currentGroup) {
alert('Por favor, seleccione un grupo de scripts primero'); 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 currentGroup = selectElement.value; // Siempre establecer currentGroup con el valor actual del select
console.log('Current group initialized as:', currentGroup); // Debug line 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 // Luego cargar el directorio de trabajo
await initWorkingDirectory(); await initWorkingDirectory();
@ -582,6 +633,13 @@
logArea.innerHTML += lines; logArea.innerHTML += lines;
logArea.scrollTop = logArea.scrollHeight; 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;
}
</script> </script>
</head> </head>
<body class="bg-gray-100"> <body class="bg-gray-100">
@ -606,11 +664,16 @@
<!-- Script Group Selection --> <!-- Script Group Selection -->
<div class="mb-8 bg-white p-6 rounded-lg shadow"> <div class="mb-8 bg-white p-6 rounded-lg shadow">
<h2 class="text-xl font-bold mb-4">Grupo de Scripts</h2> <h2 class="text-xl font-bold mb-4">Grupo de Scripts</h2>
<select id="script-group" class="w-full p-2 border rounded"> <select id="script-group" class="w-full p-2 border rounded mb-2">
{% for group in script_groups %} {% for group in script_groups %}
<option value="{{ group }}">{{ group }}</option> <option value="{{ group.id }}" data-description="{{ group.description }}">{{ group.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<p id="group-description" class="text-gray-600 text-sm italic"></p>
<div class="text-xs text-gray-500 mt-2">
<span id="group-version"></span>
<span id="group-author"></span>
</div>
</div> </div>
<!-- Level 2 Configuration --> <!-- Level 2 Configuration -->