diff --git a/backend/app.py b/backend/app.py index 1bf0035..88975d2 100644 --- a/backend/app.py +++ b/backend/app.py @@ -4,11 +4,11 @@ import sys from pathlib import Path # Add the parent directory to Python path -backend_dir = Path(__file__).parent.parent # Sube un nivel más para incluir la carpeta raíz +backend_dir = Path(__file__).parent.parent if str(backend_dir) not in sys.path: sys.path.append(str(backend_dir)) -from flask import Flask, render_template, jsonify, request, send_from_directory +from flask import Flask, render_template, jsonify, request from core.directory_handler import select_directory from core.script_manager import ScriptManager from core.profile_manager import ProfileManager @@ -46,9 +46,8 @@ def get_profile(profile_id): @app.route('/api/profiles', methods=['POST']) def create_profile(): """Create new profile""" - profile_data = request.json try: - profile = profile_manager.create_profile(profile_data) + profile = profile_manager.create_profile(request.json) return jsonify(profile) except Exception as e: return jsonify({"error": str(e)}), 400 @@ -57,13 +56,9 @@ def create_profile(): def update_profile(profile_id): """Update existing profile""" try: - profile_data = request.json - print(f"Received update request for profile {profile_id}: {profile_data}") # Debug - profile = profile_manager.update_profile(profile_id, profile_data) - print(f"Profile updated: {profile}") # Debug + profile = profile_manager.update_profile(profile_id, request.json) return jsonify(profile) except Exception as e: - print(f"Error updating profile: {e}") # Debug return jsonify({"error": str(e)}), 400 @app.route('/api/profiles/', methods=['DELETE']) @@ -75,6 +70,7 @@ def delete_profile(profile_id): except Exception as e: return jsonify({"error": str(e)}), 400 +# Script group endpoints @app.route('/api/script-groups', methods=['GET']) def get_script_groups(): """Get all available script groups""" @@ -84,110 +80,94 @@ def get_script_groups(): except Exception as e: return jsonify({"error": str(e)}), 500 -# Directory handling endpoints -@app.route('/api/select-directory', methods=['GET']) -def handle_select_directory(): - """Handle directory selection""" - print("Handling directory selection request") # Debug - result = select_directory() - print(f"Directory selection result: {result}") # Debug - if "error" in result: - return jsonify(result), 400 - return jsonify(result) - -# Script management endpoints -@app.route('/api/scripts', methods=['GET']) -def get_scripts(): - """Get all available script groups""" +@app.route('/api/script-groups//config', methods=['GET']) +def get_group_config(group_id): + """Get script group configuration""" try: - groups = script_manager.discover_groups() - return jsonify(groups) + config = script_manager.get_group_data(group_id) + return jsonify(config) except Exception as e: return jsonify({"error": str(e)}), 500 -@app.route('/api/scripts///run', methods=['POST']) -def run_script(group_id, script_id): - """Execute a specific script""" - data = request.json - work_dir = data.get('work_dir') - profile = data.get('profile') - - if not work_dir: - return jsonify({"error": "Work directory not specified"}), 400 - if not profile: - return jsonify({"error": "Profile not specified"}), 400 - +@app.route('/api/script-groups//config', methods=['PUT']) +def update_group_config(group_id): + """Update script group configuration""" try: - result = script_manager.execute_script(group_id, script_id, work_dir, profile) - return jsonify(result) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -# Work directory configuration endpoints -@app.route('/api/workdir-config/', methods=['GET']) -def get_workdir_config(work_dir): - """Get work directory configuration""" - from core.workdir_config import WorkDirConfigManager - config_manager = WorkDirConfigManager(work_dir) - return jsonify(config_manager.get_config()) - -@app.route('/api/workdir-config//group/', methods=['GET']) -def get_group_config(work_dir, group_id): - """Get group configuration from work directory""" - from core.workdir_config import WorkDirConfigManager - config_manager = WorkDirConfigManager(work_dir) - return jsonify(config_manager.get_group_config(group_id)) - -@app.route('/api/workdir-config//group/', methods=['PUT']) -def update_group_config(work_dir, group_id): - """Update group configuration in work directory""" - from core.workdir_config import WorkDirConfigManager - config_manager = WorkDirConfigManager(work_dir) - - try: - settings = request.json - config_manager.update_group_config(group_id, settings) - return jsonify({"status": "success"}) + config = script_manager.update_group_data(group_id, request.json) + return jsonify(config) except Exception as e: return jsonify({"error": str(e)}), 400 -@app.route('/api/script-groups//config-schema', methods=['PUT']) -def update_group_config_schema(group_id): - """Update configuration schema for a script group""" - try: - schema = request.json - config_file = Path(script_manager.script_groups_dir) / group_id / "config.json" - - with open(config_file, 'w', encoding='utf-8') as f: - json.dump(schema, f, indent=4) - - return jsonify({"status": "success"}) - except Exception as e: - return jsonify({"error": str(e)}), 500 - @app.route('/api/script-groups//scripts', methods=['GET']) def get_group_scripts(group_id): """Get scripts for a specific group""" try: - print(f"Loading scripts for group: {group_id}") # Debug scripts = script_manager.get_group_scripts(group_id) - print(f"Scripts found: {scripts}") # Debug return jsonify(scripts) except Exception as e: - print(f"Error loading scripts: {str(e)}") # Debug return jsonify({"error": str(e)}), 500 -@app.route('/api/script-groups//config-schema', methods=['GET']) -def get_group_config_schema(group_id): - """Get configuration schema for a script group""" +@app.route('/api/script-groups//schema', methods=['GET']) +def get_group_schema(group_id): + """Get script group schema""" try: - print(f"Loading config schema for group: {group_id}") # Debug - schema = script_manager.get_group_config_schema(group_id) - print(f"Schema loaded: {schema}") # Debug + schema = script_manager.get_global_schema() return jsonify(schema) except Exception as e: - print(f"Error loading schema: {str(e)}") # Debug + return jsonify({"error": str(e)}), 500 + +# Directory handling endpoints +@app.route('/api/select-directory', methods=['GET']) +def handle_select_directory(): + """Handle directory selection""" + result = select_directory() + if "error" in result: + return jsonify(result), 400 + return jsonify(result) + +# Work directory configuration endpoints +@app.route('/api/workdir-config/', methods=['GET']) +def get_workdir_config(group_id): + """Get work directory configuration for a group""" + try: + group_data = script_manager.get_group_data(group_id) + work_dir = group_data.get('work_dir') + + if not work_dir: + return jsonify({"error": "Work directory not configured"}), 400 + + from core.workdir_config import WorkDirConfigManager + workdir_manager = WorkDirConfigManager(work_dir, group_id) + return jsonify(workdir_manager.get_group_config()) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@app.route('/api/workdir-config/', methods=['PUT']) +def update_workdir_config(group_id): + """Update work directory configuration for a group""" + try: + group_data = script_manager.get_group_data(group_id) + work_dir = group_data.get('work_dir') + + if not work_dir: + return jsonify({"error": "Work directory not configured"}), 400 + + from core.workdir_config import WorkDirConfigManager + workdir_manager = WorkDirConfigManager(work_dir, group_id) + workdir_manager.update_group_config(request.json) + return jsonify({"status": "success"}) + except Exception as e: + return jsonify({"error": str(e)}), 400 + +# Script execution endpoint +@app.route('/api/script-groups//scripts//run', methods=['POST']) +def run_script(group_id, script_id): + """Execute a specific script""" + try: + result = script_manager.execute_script(group_id, script_id, request.json.get('profile', {})) + return jsonify(result) + except Exception as e: return jsonify({"error": str(e)}), 500 if __name__ == '__main__': - app.run(debug=True, port=5000) + app.run(debug=True, port=5000) \ No newline at end of file diff --git a/backend/core/__pycache__/config_manager.cpython-310.pyc b/backend/core/__pycache__/config_manager.cpython-310.pyc new file mode 100644 index 0000000..fc86316 Binary files /dev/null and b/backend/core/__pycache__/config_manager.cpython-310.pyc differ diff --git a/backend/core/__pycache__/profile_manager.cpython-310.pyc b/backend/core/__pycache__/profile_manager.cpython-310.pyc index 0851fe0..052daa2 100644 Binary files a/backend/core/__pycache__/profile_manager.cpython-310.pyc and b/backend/core/__pycache__/profile_manager.cpython-310.pyc differ diff --git a/backend/core/__pycache__/script_manager.cpython-310.pyc b/backend/core/__pycache__/script_manager.cpython-310.pyc index f9f5dbb..de69dd6 100644 Binary files a/backend/core/__pycache__/script_manager.cpython-310.pyc and b/backend/core/__pycache__/script_manager.cpython-310.pyc differ diff --git a/backend/core/config_manager.py b/backend/core/config_manager.py new file mode 100644 index 0000000..790ba76 --- /dev/null +++ b/backend/core/config_manager.py @@ -0,0 +1,297 @@ +# backend/core/config_manager.py +from pathlib import Path +import json +from typing import Dict, Any, Optional, List +from datetime import datetime + +class BaseConfigManager: + """Base class for all configuration managers""" + + def __init__(self, config_path: Path, schema_path: Optional[Path] = None): + self.config_path = Path(config_path) + self.schema_path = Path(schema_path) if schema_path else None + self._schema = None + self._config = None + + def _load_schema(self) -> Dict[str, Any]: + """Load configuration schema""" + if not self.schema_path or not self.schema_path.exists(): + return {"config_schema": {}} + + try: + with open(self.schema_path, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + print(f"Error loading schema: {e}") + return {"config_schema": {}} + + def _load_config(self) -> Dict[str, Any]: + """Load configuration data""" + if not self.config_path.exists(): + return {} + + try: + with open(self.config_path, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + print(f"Error loading config: {e}") + return {} + + def _save_config(self, config: Dict[str, Any]): + """Save configuration data""" + self.config_path.parent.mkdir(parents=True, exist_ok=True) + + try: + with open(self.config_path, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=4) + except Exception as e: + print(f"Error saving config: {e}") + raise + + def _validate_config(self, config: Dict[str, Any], schema: Dict[str, Any]) -> Dict[str, Any]: + """Validate configuration against schema""" + validated = {} + schema_fields = schema.get("config_schema", {}) + + for key, field_schema in schema_fields.items(): + if key in config: + validated[key] = self._validate_field(key, config[key], field_schema) + elif field_schema.get("required", False): + raise ValueError(f"Required field '{key}' is missing") + else: + validated[key] = field_schema.get("default") + + return validated + + def _validate_field(self, key: str, value: Any, field_schema: Dict[str, Any]) -> Any: + """Validate a single field value""" + field_type = field_schema.get("type") + + if value is None or value == "": + if field_schema.get("required", False): + raise ValueError(f"Field '{key}' is required") + return field_schema.get("default") + + try: + if field_type == "string": + return str(value) + elif field_type == "number": + return float(value) if "." in str(value) else int(value) + elif field_type == "boolean": + if isinstance(value, str): + return value.lower() == "true" + return bool(value) + elif field_type == "directory": + path = Path(value) + if not path.is_absolute(): + path = Path(self.config_path).parent / path + path.mkdir(parents=True, exist_ok=True) + return str(path) + elif field_type == "select": + if value not in field_schema.get("options", []): + raise ValueError(f"Invalid option '{value}' for field '{key}'") + return value + else: + return value + except Exception as e: + raise ValueError(f"Invalid value for field '{key}': {str(e)}") + + def get_schema(self) -> Dict[str, Any]: + """Get configuration schema""" + if self._schema is None: + self._schema = self._load_schema() + return self._schema + + def get_config(self) -> Dict[str, Any]: + """Get current configuration""" + if self._config is None: + self._config = self._load_config() + return self._config + + def update_schema(self, schema: Dict[str, Any]): + """Update configuration schema""" + if not self.schema_path: + raise ValueError("No schema path configured") + + try: + self.schema_path.parent.mkdir(parents=True, exist_ok=True) + with open(self.schema_path, 'w', encoding='utf-8') as f: + json.dump(schema, f, indent=4) + self._schema = schema + except Exception as e: + print(f"Error saving schema: {e}") + raise + +class ProfileManager(BaseConfigManager): + """Manager for application profiles""" + + DEFAULT_PROFILE = { + "id": "default", + "name": "Default Profile", + "llm_settings": { + "model": "gpt-4", + "temperature": 0.7, + "api_key": "" + } + } + + def __init__(self, data_dir: Path): + super().__init__( + config_path=data_dir / "profiles.json", + schema_path=data_dir / "profile_schema.json" + ) + self.profiles = self._load_profiles() + + def _load_profiles(self) -> Dict[str, Dict]: + """Load all profiles""" + if self.config_path.exists(): + try: + with open(self.config_path, 'r', encoding='utf-8') as f: + profiles = json.load(f) + if "default" not in profiles: + profiles["default"] = self._create_default_profile() + return profiles + except Exception as e: + print(f"Error loading profiles: {e}") + return {"default": self._create_default_profile()} + else: + profiles = {"default": self._create_default_profile()} + self._save_profiles(profiles) + return profiles + + def _create_default_profile(self) -> Dict[str, Any]: + """Create default profile""" + profile = self.DEFAULT_PROFILE.copy() + now = datetime.now().isoformat() + profile["created_at"] = now + profile["updated_at"] = now + return profile + + def _save_profiles(self, profiles: Dict[str, Dict]): + """Save all profiles""" + try: + self.config_path.parent.mkdir(parents=True, exist_ok=True) + with open(self.config_path, 'w', encoding='utf-8') as f: + json.dump(profiles, f, indent=4) + except Exception as e: + print(f"Error saving profiles: {e}") + raise + + def get_all_profiles(self) -> List[Dict[str, Any]]: + """Get all profiles""" + return list(self.profiles.values()) + + def get_profile(self, profile_id: str) -> Optional[Dict[str, Any]]: + """Get specific profile""" + return self.profiles.get(profile_id) + + def create_profile(self, profile_data: Dict[str, Any]) -> Dict[str, Any]: + """Create new profile""" + if "id" not in profile_data: + raise ValueError("Profile must have an id") + + profile_id = profile_data["id"] + if profile_id in self.profiles: + raise ValueError(f"Profile {profile_id} already exists") + + # Add timestamps + now = datetime.now().isoformat() + profile_data["created_at"] = now + profile_data["updated_at"] = now + + # Validate against schema + schema = self.get_schema() + validated_data = self._validate_config(profile_data, schema) + + # Add to profiles + self.profiles[profile_id] = validated_data + self._save_profiles(self.profiles) + return validated_data + + def update_profile(self, profile_id: str, profile_data: Dict[str, Any]) -> Dict[str, Any]: + """Update existing profile""" + if profile_id not in self.profiles: + raise ValueError(f"Profile {profile_id} not found") + + if profile_id == "default" and "id" in profile_data: + raise ValueError("Cannot change id of default profile") + + # Update timestamp + profile_data["updated_at"] = datetime.now().isoformat() + + # Validate against schema + schema = self.get_schema() + validated_data = self._validate_config(profile_data, schema) + + # Update profile + self.profiles[profile_id].update(validated_data) + self._save_profiles(self.profiles) + return self.profiles[profile_id] + + def delete_profile(self, profile_id: str): + """Delete profile""" + if profile_id == "default": + raise ValueError("Cannot delete default profile") + + if profile_id not in self.profiles: + raise ValueError(f"Profile {profile_id} not found") + + del self.profiles[profile_id] + self._save_profiles(self.profiles) + +class ScriptGroupManager(BaseConfigManager): + """Manager for script group configuration""" + + def __init__(self, group_dir: Path): + super().__init__( + config_path=group_dir / "data.json", + schema_path=group_dir.parent / "config.json" + ) + + def get_work_dir(self) -> Optional[str]: + """Get work directory from configuration""" + config = self.get_config() + return config.get("work_dir") + + def update_config(self, config_data: Dict[str, Any]) -> Dict[str, Any]: + """Update configuration""" + # Add timestamp + config_data["updated_at"] = datetime.now().isoformat() + + # Validate against schema + schema = self.get_schema() + validated_data = self._validate_config(config_data, schema) + + # Save configuration + self._save_config(validated_data) + self._config = validated_data + return validated_data + +class WorkDirConfigManager(BaseConfigManager): + """Manager for work directory configuration""" + + def __init__(self, work_dir: str, group_id: str): + self.work_dir = Path(work_dir) + self.group_id = group_id + super().__init__( + config_path=self.work_dir / "script_config.json", + schema_path=None # Schema is loaded from group configuration + ) + + def get_group_config(self) -> Dict[str, Any]: + """Get configuration for current group""" + config = self.get_config() + return config.get("group_settings", {}).get(self.group_id, {}) + + def update_group_config(self, settings: Dict[str, Any]): + """Update configuration for current group""" + config = self.get_config() + + if "group_settings" not in config: + config["group_settings"] = {} + + config["group_settings"][self.group_id] = settings + config["updated_at"] = datetime.now().isoformat() + + self._save_config(config) + self._config = config \ No newline at end of file diff --git a/backend/core/profile_manager.py b/backend/core/profile_manager.py index 3baa562..6ba30d5 100644 --- a/backend/core/profile_manager.py +++ b/backend/core/profile_manager.py @@ -1,4 +1,3 @@ -# backend/core/profile_manager.py from pathlib import Path import json from typing import Dict, Any, List, Optional @@ -6,44 +5,55 @@ from datetime import datetime class ProfileManager: - """Manages configuration profiles""" + """Manages application profiles""" DEFAULT_PROFILE = { "id": "default", "name": "Default Profile", "llm_settings": {"model": "gpt-4", "temperature": 0.7, "api_key": ""}, - "created_at": "", - "updated_at": "", } def __init__(self, data_dir: Path): self.data_dir = data_dir self.profiles_file = data_dir / "profiles.json" + self.schema_file = data_dir / "profile_schema.json" self.profiles: Dict[str, Dict] = self._load_profiles() + self._schema = self._load_schema() + + def _load_schema(self) -> Dict[str, Any]: + """Load profile schema""" + if self.schema_file.exists(): + try: + with open(self.schema_file, "r", encoding="utf-8") as f: + return json.load(f) + except Exception as e: + print(f"Error loading profile schema: {e}") + return {"config_schema": {}} def _load_profiles(self) -> Dict[str, Dict]: - """Load profiles from file""" - if self.profiles_file.exists(): - try: + """Load all profiles and ensure default profile exists""" + profiles = {} + + # Crear perfil por defecto si no existe + default_profile = self._create_default_profile() + + try: + if self.profiles_file.exists(): with open(self.profiles_file, "r", encoding="utf-8") as f: - profiles = json.load(f) - # Ensure default profile exists - if "default" not in profiles: - profiles["default"] = self._create_default_profile() - return profiles - except Exception as e: - print(f"Error loading profiles: {e}") - return {"default": self._create_default_profile()} - else: - # Create directory if it doesn't exist - self.profiles_file.parent.mkdir(parents=True, exist_ok=True) - # Create default profile - profiles = {"default": self._create_default_profile()} + loaded_profiles = json.load(f) + profiles.update(loaded_profiles) + except Exception as e: + print(f"Error loading profiles: {e}") + + # Asegurar que existe el perfil por defecto + if "default" not in profiles: + profiles["default"] = default_profile self._save_profiles(profiles) - return profiles + + return profiles def _create_default_profile(self) -> Dict[str, Any]: - """Create default profile with timestamp""" + """Create default profile""" profile = self.DEFAULT_PROFILE.copy() now = datetime.now().isoformat() profile["created_at"] = now @@ -51,25 +61,87 @@ class ProfileManager: return profile def _save_profiles(self, profiles: Optional[Dict] = None): - """Save profiles to file""" + """Save all profiles""" if profiles is None: profiles = self.profiles try: - print(f"Saving profiles to: {self.profiles_file}") # Agregar debug + self.profiles_file.parent.mkdir(parents=True, exist_ok=True) with open(self.profiles_file, "w", encoding="utf-8") as f: json.dump(profiles, f, indent=4) - print("Profiles saved successfully") # Agregar debug except Exception as e: - print(f"Error saving profiles: {e}") # Agregar debug - raise # Re-lanzar la excepción para que se maneje arriba + print(f"Error saving profiles: {e}") + raise + + def _validate_profile(self, profile_data: Dict[str, Any]) -> Dict[str, Any]: + """Validate profile data against schema""" + schema = self._schema.get("config_schema", {}) + validated = {} + + for key, field_schema in schema.items(): + if key in profile_data: + validated[key] = self._validate_field( + key, profile_data[key], field_schema + ) + elif field_schema.get("required", False): + raise ValueError(f"Required field '{key}' is missing") + else: + validated[key] = field_schema.get("default") + + # Pass through non-schema fields + for key, value in profile_data.items(): + if key not in schema: + validated[key] = value + + return validated + + def _validate_field( + self, key: str, value: Any, field_schema: Dict[str, Any] + ) -> Any: + """Validate a single field value""" + field_type = field_schema.get("type") + + if value is None or value == "": + if field_schema.get("required", False): + raise ValueError(f"Field '{key}' is required") + return field_schema.get("default") + + try: + if field_type == "string": + return str(value) + elif field_type == "number": + return float(value) if "." in str(value) else int(value) + elif field_type == "boolean": + if isinstance(value, str): + return value.lower() == "true" + return bool(value) + elif field_type == "select": + if value not in field_schema.get("options", []): + raise ValueError(f"Invalid option '{value}' for field '{key}'") + return value + else: + return value + except Exception as e: + raise ValueError(f"Invalid value for field '{key}': {str(e)}") def get_all_profiles(self) -> List[Dict[str, Any]]: """Get all profiles""" return list(self.profiles.values()) def get_profile(self, profile_id: str) -> Optional[Dict[str, Any]]: - """Get specific profile""" - return self.profiles.get(profile_id) + """Get specific profile with fallback to default""" + profile = self.profiles.get(profile_id) + + if not profile and profile_id != "default": + # Si no se encuentra el perfil y no es el perfil por defecto, + # intentar retornar el perfil por defecto + profile = self.profiles.get("default") + if not profile: + # Si tampoco existe el perfil por defecto, crearlo + profile = self._create_default_profile() + self.profiles["default"] = profile + self._save_profiles(self.profiles) + + return profile def create_profile(self, profile_data: Dict[str, Any]) -> Dict[str, Any]: """Create new profile""" @@ -85,41 +157,36 @@ class ProfileManager: profile_data["created_at"] = now profile_data["updated_at"] = now - # Ensure required fields - for key in ["name", "llm_settings"]: - if key not in profile_data: - profile_data[key] = self.DEFAULT_PROFILE[key] + # Validate profile data + validated_data = self._validate_profile(profile_data) - self.profiles[profile_id] = profile_data + # Save profile + self.profiles[profile_id] = validated_data self._save_profiles() - return profile_data + return validated_data def update_profile( self, profile_id: str, profile_data: Dict[str, Any] ) -> Dict[str, Any]: """Update existing profile""" - try: - print(f"Updating profile {profile_id} with data: {profile_data}") - if profile_id not in self.profiles: - raise ValueError(f"Profile {profile_id} not found") + if profile_id not in self.profiles: + raise ValueError(f"Profile {profile_id} not found") - if profile_id == "default" and "id" in profile_data: - raise ValueError("Cannot change id of default profile") + if profile_id == "default" and "id" in profile_data: + raise ValueError("Cannot change id of default profile") - # Update timestamp - profile_data["updated_at"] = datetime.now().isoformat() + # Update timestamp + profile_data["updated_at"] = datetime.now().isoformat() - # Update profile - current_profile = self.profiles[profile_id].copy() # Hacer una copia - current_profile.update(profile_data) # Actualizar la copia - self.profiles[profile_id] = current_profile # Asignar la copia actualizada + # Validate profile data + validated_data = self._validate_profile(profile_data) - print(f"Updated profile: {self.profiles[profile_id]}") - self._save_profiles() - return self.profiles[profile_id] - except Exception as e: - print(f"Error in update_profile: {e}") # Agregar debug - raise + # Update profile + current_profile = self.profiles[profile_id].copy() + current_profile.update(validated_data) + self.profiles[profile_id] = current_profile + self._save_profiles() + return current_profile def delete_profile(self, profile_id: str): """Delete profile""" @@ -131,3 +198,18 @@ class ProfileManager: del self.profiles[profile_id] self._save_profiles() + + def get_schema(self) -> Dict[str, Any]: + """Get profile schema""" + return self._schema + + def update_schema(self, schema: Dict[str, Any]): + """Update profile schema""" + try: + self.schema_file.parent.mkdir(parents=True, exist_ok=True) + with open(self.schema_file, "w", encoding="utf-8") as f: + json.dump(schema, f, indent=4) + self._schema = schema + except Exception as e: + print(f"Error saving schema: {e}") + raise diff --git a/backend/core/script_manager.py b/backend/core/script_manager.py index 03d83a6..e9c3bdd 100644 --- a/backend/core/script_manager.py +++ b/backend/core/script_manager.py @@ -1,131 +1,163 @@ -# backend/core/script_manager.py from pathlib import Path import importlib.util import inspect from typing import Dict, List, Any, Optional import json -from .group_settings_manager import GroupSettingsManager # Agregar esta importación - +from datetime import datetime class ScriptManager: + """Manages script groups and their execution""" + def __init__(self, script_groups_dir: Path): self.script_groups_dir = script_groups_dir - self.group_settings = GroupSettingsManager(script_groups_dir) + self._global_schema = self._load_global_schema() - def get_group_settings(self, group_id: str) -> Dict[str, Any]: - """Get settings for a script group""" - return self.group_settings.get_group_settings(group_id) + def _load_global_schema(self) -> Dict[str, Any]: + """Load global configuration schema for script groups""" + schema_file = self.script_groups_dir / "config.json" + try: + with open(schema_file, "r", encoding="utf-8") as f: + return json.load(f) + except Exception as e: + print(f"Error loading global schema: {e}") + return {"config_schema": {}} - def update_group_settings(self, group_id: str, settings: Dict[str, Any]): - """Update settings for a script group""" - return self.group_settings.update_group_settings(group_id, settings) + def _load_group_data(self, group_id: str) -> Dict[str, Any]: + """Load group data""" + data_file = self.script_groups_dir / group_id / "data.json" + try: + with open(data_file, "r", encoding="utf-8") as f: + return json.load(f) + except Exception as e: + print(f"Error loading group data: {e}") + return {} - def get_group_config_schema(self, group_id: str) -> Dict[str, Any]: - """Get configuration schema for a script group""" - config_file = self.script_groups_dir / group_id / "config.json" - print(f"Looking for config file: {config_file}") # Debug + def _save_group_data(self, group_id: str, data: Dict[str, Any]): + """Save group data""" + data_file = self.script_groups_dir / group_id / "data.json" + try: + data_file.parent.mkdir(parents=True, exist_ok=True) + with open(data_file, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4) + except Exception as e: + print(f"Error saving group data: {e}") + raise - if config_file.exists(): - try: - with open(config_file, "r", encoding="utf-8") as f: - schema = json.load(f) - print(f"Loaded schema: {schema}") # Debug - return schema - except Exception as e: - print(f"Error loading group config schema: {e}") # Debug - else: - print(f"Config file not found: {config_file}") # Debug + def _validate_group_data(self, data: Dict[str, Any]) -> Dict[str, Any]: + """Validate group data against schema""" + schema = self._global_schema.get("config_schema", {}) + validated = {} - # Retornar un schema vacío si no existe el archivo - return {"group_name": group_id, "description": "", "config_schema": {}} + for key, field_schema in schema.items(): + if key in data: + validated[key] = self._validate_field(key, data[key], field_schema) + elif field_schema.get("required", False): + raise ValueError(f"Required field '{key}' is missing") + else: + validated[key] = field_schema.get("default") + + return validated + + def _validate_field(self, key: str, value: Any, field_schema: Dict[str, Any]) -> Any: + """Validate a single field value""" + field_type = field_schema.get("type") + + if value is None or value == "": + if field_schema.get("required", False): + raise ValueError(f"Field '{key}' is required") + return field_schema.get("default") + + try: + if field_type == "string": + return str(value) + elif field_type == "number": + return float(value) if "." in str(value) else int(value) + elif field_type == "boolean": + if isinstance(value, str): + return value.lower() == "true" + return bool(value) + elif field_type == "directory": + path = Path(value) + if not path.is_absolute(): + path = self.script_groups_dir / path + path.mkdir(parents=True, exist_ok=True) + return str(path) + elif field_type == "select": + if value not in field_schema.get("options", []): + raise ValueError(f"Invalid option '{value}' for field '{key}'") + return value + else: + return value + except Exception as e: + raise ValueError(f"Invalid value for field '{key}': {str(e)}") def get_available_groups(self) -> List[Dict[str, Any]]: - """Get list of available script groups""" + """Get all available script groups""" groups = [] - for group_dir in self.script_groups_dir.iterdir(): if group_dir.is_dir() and not group_dir.name.startswith("_"): - groups.append( - { + group_data = self._load_group_data(group_dir.name) + if group_data: + groups.append({ "id": group_dir.name, - "name": group_dir.name.replace("_", " ").title(), - "path": str(group_dir), - } - ) - + "name": group_data.get("name", group_dir.name), + "description": group_data.get("description", ""), + "work_dir": group_data.get("work_dir", ""), + "enabled": group_data.get("enabled", True) + }) return groups + def get_group_data(self, group_id: str) -> Dict[str, Any]: + """Get group configuration data""" + return self._load_group_data(group_id) + + def update_group_data(self, group_id: str, data: Dict[str, Any]) -> Dict[str, Any]: + """Update group configuration data""" + # Validar datos + validated_data = self._validate_group_data(data) + + # Actualizar timestamps + validated_data["updated_at"] = datetime.now().isoformat() + if not self._load_group_data(group_id): + validated_data["created_at"] = validated_data["updated_at"] + + # Guardar datos + self._save_group_data(group_id, validated_data) + return validated_data + def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: """Get scripts for a specific group""" group_dir = self.script_groups_dir / group_id - print(f"Looking for scripts in: {group_dir}") # Debug - if not group_dir.exists() or not group_dir.is_dir(): - print(f"Directory not found: {group_dir}") # Debug raise ValueError(f"Script group '{group_id}' not found") scripts = [] for script_file in group_dir.glob("x[0-9].py"): - print(f"Found script file: {script_file}") # Debug script_info = self._analyze_script(script_file) if script_info: scripts.append(script_info) return sorted(scripts, key=lambda x: x["id"]) - def discover_groups(self) -> List[Dict[str, Any]]: - """Discover all script groups""" - groups = [] - - for group_dir in self.script_groups_dir.iterdir(): - if group_dir.is_dir() and not group_dir.name.startswith("_"): - group_info = self._analyze_group(group_dir) - if group_info: - groups.append(group_info) - - return groups - - def _analyze_group(self, group_dir: Path) -> Optional[Dict[str, Any]]: - """Analyze a script group directory""" - scripts = [] - - for script_file in group_dir.glob("x[0-9].py"): - try: - script_info = self._analyze_script(script_file) - if script_info: - scripts.append(script_info) - except Exception as e: - print(f"Error analyzing script {script_file}: {e}") - - if scripts: - return { - "id": group_dir.name, - "name": group_dir.name.replace("_", " ").title(), - "scripts": sorted(scripts, key=lambda x: x["id"]), - } - return None - def _analyze_script(self, script_file: Path) -> Optional[Dict[str, Any]]: """Analyze a single script file""" try: - # Import script module + # Importar módulo del script spec = importlib.util.spec_from_file_location(script_file.stem, script_file) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - # Find script class + # Encontrar la clase del script script_class = None for name, obj in inspect.getmembers(module): - if ( - inspect.isclass(obj) - and obj.__module__ == module.__name__ - and hasattr(obj, "run") - ): + if (inspect.isclass(obj) and + obj.__module__ == module.__name__ and + hasattr(obj, "run")): script_class = obj break if script_class: - # Extraer la primera línea del docstring como nombre + # Extraer nombre y descripción del docstring docstring = inspect.getdoc(script_class) if docstring: name, *description = docstring.split("\n", 1) @@ -138,44 +170,38 @@ class ScriptManager: "id": script_file.stem, "name": name.strip(), "description": description.strip(), - "file": str(script_file.relative_to(self.script_groups_dir)), + "file": str(script_file.relative_to(self.script_groups_dir)) } except Exception as e: print(f"Error loading script {script_file}: {e}") + return None - return None - - def execute_script( - self, group_id: str, script_id: str, profile: Dict[str, Any] - ) -> Dict[str, Any]: + def execute_script(self, group_id: str, script_id: str, profile: Dict[str, Any]) -> Dict[str, Any]: """Execute a specific script""" - # Get group settings first - group_settings = self.group_settings.get_group_settings(group_id) - work_dir = group_settings.get("work_dir") + # Obtener datos del grupo + group_data = self._load_group_data(group_id) + work_dir = group_data.get("work_dir") if not work_dir: raise ValueError(f"No work directory configured for group {group_id}") script_file = self.script_groups_dir / group_id / f"{script_id}.py" - if not script_file.exists(): raise ValueError(f"Script {script_id} not found in group {group_id}") try: - # Import script module + # Importar módulo del script spec = importlib.util.spec_from_file_location(script_id, script_file) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - # Find and instantiate script class + # Encontrar e instanciar la clase del script script_class = None for name, obj in inspect.getmembers(module): - if ( - inspect.isclass(obj) - and obj.__module__ == module.__name__ - and hasattr(obj, "run") - ): + if (inspect.isclass(obj) and + obj.__module__ == module.__name__ and + hasattr(obj, "run")): script_class = obj break @@ -187,3 +213,7 @@ class ScriptManager: except Exception as e: return {"status": "error", "error": str(e)} + + def get_global_schema(self) -> Dict[str, Any]: + """Get global configuration schema""" + return self._global_schema \ No newline at end of file diff --git a/backend/script_groups/base_script.py b/backend/script_groups/base_script.py index 2a34b9a..fae5720 100644 --- a/backend/script_groups/base_script.py +++ b/backend/script_groups/base_script.py @@ -2,6 +2,7 @@ from typing import Dict, Any from pathlib import Path import json +from datetime import datetime class BaseScript: """Base class for all scripts""" @@ -19,8 +20,8 @@ class BaseScript: """ raise NotImplementedError("Script must implement run method") - def get_config(self, work_dir: str, group_id: str) -> Dict[str, Any]: - """Get group configuration from work directory""" + def get_work_config(self, work_dir: str, group_id: str) -> Dict[str, Any]: + """Get configuration from work directory""" config_file = Path(work_dir) / "script_config.json" if config_file.exists(): @@ -29,33 +30,49 @@ class BaseScript: config = json.load(f) return config.get("group_settings", {}).get(group_id, {}) except Exception as e: - print(f"Error loading config: {e}") + print(f"Error loading work directory config: {e}") return {} - def save_config(self, work_dir: str, group_id: str, settings: Dict[str, Any]): - """Save group configuration to work directory""" + def save_work_config(self, work_dir: str, group_id: str, settings: Dict[str, Any]): + """Save configuration to work directory""" config_file = Path(work_dir) / "script_config.json" try: - # Load existing config or create new + # Cargar configuración existente o crear nueva if config_file.exists(): with open(config_file, 'r', encoding='utf-8') as f: config = json.load(f) else: config = { "version": "1.0", - "group_settings": {} + "group_settings": {}, + "created_at": datetime.now().isoformat() } - # Update settings + # Actualizar configuración if "group_settings" not in config: config["group_settings"] = {} config["group_settings"][group_id] = settings + config["updated_at"] = datetime.now().isoformat() - # Save config + # Guardar configuración with open(config_file, 'w', encoding='utf-8') as f: json.dump(config, f, indent=4) except Exception as e: - print(f"Error saving config: {e}") + print(f"Error saving work directory config: {e}") + raise + + def get_group_data(self, group_dir: Path) -> Dict[str, Any]: + """Get group configuration data""" + data_file = group_dir / "data.json" + + if data_file.exists(): + try: + with open(data_file, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + print(f"Error loading group data: {e}") + + return {} \ No newline at end of file diff --git a/backend/script_groups/config.json b/backend/script_groups/config.json index 4886062..cf7d8e6 100644 --- a/backend/script_groups/config.json +++ b/backend/script_groups/config.json @@ -2,29 +2,46 @@ "description": "Configuration schema for script groups", "config_schema": { "work_dir": { - "type": "string", + "type": "directory", "description": "Working directory for this script group", "required": true }, + "name": { + "type": "string", + "description": "Display name for the script group", + "required": true + }, "description": { "type": "string", - "description": "Description of this script group", + "description": "Detailed description of the script group", "default": "" }, - "backup_dir": { - "type": "directory", - "description": "Backup directory path", - "default": "" - }, - "max_files": { - "type": "number", - "description": "Maximum number of files to process", - "default": 1000 - }, - "enable_backup": { + "enabled": { "type": "boolean", - "description": "Enable automatic backups", - "default": false + "description": "Whether this script group is enabled", + "default": true + }, + "execution_mode": { + "type": "select", + "description": "Execution mode for scripts in this group", + "options": ["sequential", "parallel"], + "default": "sequential" + }, + "max_parallel": { + "type": "number", + "description": "Maximum number of parallel executions (if applicable)", + "default": 4 + }, + "timeout": { + "type": "number", + "description": "Script execution timeout in seconds", + "default": 3600 + }, + "logging": { + "type": "select", + "description": "Logging level for script execution", + "options": ["debug", "info", "warning", "error"], + "default": "info" } } -} +} \ No newline at end of file diff --git a/backend/script_groups/data.json b/backend/script_groups/data.json new file mode 100644 index 0000000..343788b --- /dev/null +++ b/backend/script_groups/data.json @@ -0,0 +1,12 @@ +{ + "name": "System Analysis", + "description": "Scripts for system analysis and file management", + "work_dir": "", + "enabled": true, + "execution_mode": "sequential", + "max_parallel": 4, + "timeout": 3600, + "logging": "info", + "created_at": "2025-02-08T12:00:00.000Z", + "updated_at": "2025-02-08T12:00:00.000Z" +} \ No newline at end of file diff --git a/claude/__init__.py b/claude/__init__.py deleted file mode 100644 index a1de208..0000000 --- a/claude/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# backend/__init__.py diff --git a/claude/__init___1.py b/claude/__init___1.py deleted file mode 100644 index d258405..0000000 --- a/claude/__init___1.py +++ /dev/null @@ -1 +0,0 @@ -# backend/core/__init__.py diff --git a/claude/__init___2.py b/claude/__init___2.py deleted file mode 100644 index 772a730..0000000 --- a/claude/__init___2.py +++ /dev/null @@ -1 +0,0 @@ -# backend/script_groups/__init__.py diff --git a/claude/__init___3.py b/claude/__init___3.py deleted file mode 100644 index 4e7ed26..0000000 --- a/claude/__init___3.py +++ /dev/null @@ -1 +0,0 @@ -# backend/script_groups/example_group/__init__.py diff --git a/claude/app.py b/claude/app.py deleted file mode 100644 index 1bf0035..0000000 --- a/claude/app.py +++ /dev/null @@ -1,193 +0,0 @@ -# backend/app.py -import os -import sys -from pathlib import Path - -# Add the parent directory to Python path -backend_dir = Path(__file__).parent.parent # Sube un nivel más para incluir la carpeta raíz -if str(backend_dir) not in sys.path: - sys.path.append(str(backend_dir)) - -from flask import Flask, render_template, jsonify, request, send_from_directory -from core.directory_handler import select_directory -from core.script_manager import ScriptManager -from core.profile_manager import ProfileManager - -app = Flask(__name__, - template_folder='../frontend/templates', - static_folder='../frontend/static') - -# Initialize managers -data_dir = Path(__file__).parent.parent / 'data' -script_groups_dir = Path(__file__).parent / 'script_groups' - -profile_manager = ProfileManager(data_dir) -script_manager = ScriptManager(script_groups_dir) - -@app.route('/') -def index(): - """Render main page""" - return render_template('index.html') - -# Profile endpoints -@app.route('/api/profiles', methods=['GET']) -def get_profiles(): - """Get all profiles""" - return jsonify(profile_manager.get_all_profiles()) - -@app.route('/api/profiles/', methods=['GET']) -def get_profile(profile_id): - """Get specific profile""" - profile = profile_manager.get_profile(profile_id) - if profile: - return jsonify(profile) - return jsonify({"error": "Profile not found"}), 404 - -@app.route('/api/profiles', methods=['POST']) -def create_profile(): - """Create new profile""" - profile_data = request.json - try: - profile = profile_manager.create_profile(profile_data) - return jsonify(profile) - except Exception as e: - return jsonify({"error": str(e)}), 400 - -@app.route('/api/profiles/', methods=['PUT']) -def update_profile(profile_id): - """Update existing profile""" - try: - profile_data = request.json - print(f"Received update request for profile {profile_id}: {profile_data}") # Debug - profile = profile_manager.update_profile(profile_id, profile_data) - print(f"Profile updated: {profile}") # Debug - return jsonify(profile) - except Exception as e: - print(f"Error updating profile: {e}") # Debug - return jsonify({"error": str(e)}), 400 - -@app.route('/api/profiles/', methods=['DELETE']) -def delete_profile(profile_id): - """Delete profile""" - try: - profile_manager.delete_profile(profile_id) - return jsonify({"status": "success"}) - except Exception as e: - return jsonify({"error": str(e)}), 400 - -@app.route('/api/script-groups', methods=['GET']) -def get_script_groups(): - """Get all available script groups""" - try: - groups = script_manager.get_available_groups() - return jsonify(groups) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -# Directory handling endpoints -@app.route('/api/select-directory', methods=['GET']) -def handle_select_directory(): - """Handle directory selection""" - print("Handling directory selection request") # Debug - result = select_directory() - print(f"Directory selection result: {result}") # Debug - if "error" in result: - return jsonify(result), 400 - return jsonify(result) - -# Script management endpoints -@app.route('/api/scripts', methods=['GET']) -def get_scripts(): - """Get all available script groups""" - try: - groups = script_manager.discover_groups() - return jsonify(groups) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -@app.route('/api/scripts///run', methods=['POST']) -def run_script(group_id, script_id): - """Execute a specific script""" - data = request.json - work_dir = data.get('work_dir') - profile = data.get('profile') - - if not work_dir: - return jsonify({"error": "Work directory not specified"}), 400 - if not profile: - return jsonify({"error": "Profile not specified"}), 400 - - try: - result = script_manager.execute_script(group_id, script_id, work_dir, profile) - return jsonify(result) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -# Work directory configuration endpoints -@app.route('/api/workdir-config/', methods=['GET']) -def get_workdir_config(work_dir): - """Get work directory configuration""" - from core.workdir_config import WorkDirConfigManager - config_manager = WorkDirConfigManager(work_dir) - return jsonify(config_manager.get_config()) - -@app.route('/api/workdir-config//group/', methods=['GET']) -def get_group_config(work_dir, group_id): - """Get group configuration from work directory""" - from core.workdir_config import WorkDirConfigManager - config_manager = WorkDirConfigManager(work_dir) - return jsonify(config_manager.get_group_config(group_id)) - -@app.route('/api/workdir-config//group/', methods=['PUT']) -def update_group_config(work_dir, group_id): - """Update group configuration in work directory""" - from core.workdir_config import WorkDirConfigManager - config_manager = WorkDirConfigManager(work_dir) - - try: - settings = request.json - config_manager.update_group_config(group_id, settings) - return jsonify({"status": "success"}) - except Exception as e: - return jsonify({"error": str(e)}), 400 - -@app.route('/api/script-groups//config-schema', methods=['PUT']) -def update_group_config_schema(group_id): - """Update configuration schema for a script group""" - try: - schema = request.json - config_file = Path(script_manager.script_groups_dir) / group_id / "config.json" - - with open(config_file, 'w', encoding='utf-8') as f: - json.dump(schema, f, indent=4) - - return jsonify({"status": "success"}) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -@app.route('/api/script-groups//scripts', methods=['GET']) -def get_group_scripts(group_id): - """Get scripts for a specific group""" - try: - print(f"Loading scripts for group: {group_id}") # Debug - scripts = script_manager.get_group_scripts(group_id) - print(f"Scripts found: {scripts}") # Debug - return jsonify(scripts) - except Exception as e: - print(f"Error loading scripts: {str(e)}") # Debug - return jsonify({"error": str(e)}), 500 - -@app.route('/api/script-groups//config-schema', methods=['GET']) -def get_group_config_schema(group_id): - """Get configuration schema for a script group""" - try: - print(f"Loading config schema for group: {group_id}") # Debug - schema = script_manager.get_group_config_schema(group_id) - print(f"Schema loaded: {schema}") # Debug - return jsonify(schema) - except Exception as e: - print(f"Error loading schema: {str(e)}") # Debug - return jsonify({"error": str(e)}), 500 - -if __name__ == '__main__': - app.run(debug=True, port=5000) diff --git a/claude/base.html b/claude/base.html deleted file mode 100644 index 4282250..0000000 --- a/claude/base.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - Local Scripts Web - - - - - {% block content %}{% endblock %} - - \ No newline at end of file diff --git a/claude/base_script.py b/claude/base_script.py deleted file mode 100644 index 2a34b9a..0000000 --- a/claude/base_script.py +++ /dev/null @@ -1,61 +0,0 @@ -# backend/script_groups/base_script.py -from typing import Dict, Any -from pathlib import Path -import json - -class BaseScript: - """Base class for all scripts""" - - def run(self, work_dir: str, profile: Dict[str, Any]) -> Dict[str, Any]: - """ - Execute the script - - Args: - work_dir (str): Working directory path - profile (Dict[str, Any]): Current profile configuration - - Returns: - Dict[str, Any]: Execution results - """ - raise NotImplementedError("Script must implement run method") - - def get_config(self, work_dir: str, group_id: str) -> Dict[str, Any]: - """Get group configuration from work directory""" - config_file = Path(work_dir) / "script_config.json" - - if config_file.exists(): - try: - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - return config.get("group_settings", {}).get(group_id, {}) - except Exception as e: - print(f"Error loading config: {e}") - - return {} - - def save_config(self, work_dir: str, group_id: str, settings: Dict[str, Any]): - """Save group configuration to work directory""" - config_file = Path(work_dir) / "script_config.json" - - try: - # Load existing config or create new - if config_file.exists(): - with open(config_file, 'r', encoding='utf-8') as f: - config = json.load(f) - else: - config = { - "version": "1.0", - "group_settings": {} - } - - # Update settings - if "group_settings" not in config: - config["group_settings"] = {} - config["group_settings"][group_id] = settings - - # Save config - with open(config_file, 'w', encoding='utf-8') as f: - json.dump(config, f, indent=4) - - except Exception as e: - print(f"Error saving config: {e}") diff --git a/claude/claude_file_organizer.py b/claude/claude_file_organizer.py deleted file mode 100644 index 93391ad..0000000 --- a/claude/claude_file_organizer.py +++ /dev/null @@ -1,172 +0,0 @@ -# claude_file_organizer.py -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': '', - } - 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('') + 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() \ No newline at end of file diff --git a/claude/config.json b/claude/config.json deleted file mode 100644 index 4886062..0000000 --- a/claude/config.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "description": "Configuration schema for script groups", - "config_schema": { - "work_dir": { - "type": "string", - "description": "Working directory for this script group", - "required": true - }, - "description": { - "type": "string", - "description": "Description of this script group", - "default": "" - }, - "backup_dir": { - "type": "directory", - "description": "Backup directory path", - "default": "" - }, - "max_files": { - "type": "number", - "description": "Maximum number of files to process", - "default": 1000 - }, - "enable_backup": { - "type": "boolean", - "description": "Enable automatic backups", - "default": false - } - } -} diff --git a/claude/config_1.json b/claude/config_1.json deleted file mode 100644 index 36be0b3..0000000 --- a/claude/config_1.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "group_name": "System Analysis", - "description": "Scripts for system analysis and file management", - "config_schema": { - "exclude_dirs": { - "type": "string", - "description": "Directories to exclude (comma separated)", - "default": "venv,__pycache__,.git" - }, - "count_hidden": { - "type": "boolean", - "description": "Include hidden files in count", - "default": false - }, - "min_size": { - "type": "number", - "description": "Minimum file size to count (in bytes)", - "default": 0 - }, - "save_report": { - "type": "boolean", - "description": "Save results to file", - "default": true - }, - "report_format": { - "type": "select", - "options": ["txt", "json", "csv"], - "description": "Format for saved reports", - "default": "json" - } - } -} \ No newline at end of file diff --git a/claude/directory_handler.py b/claude/directory_handler.py deleted file mode 100644 index 53f5ad9..0000000 --- a/claude/directory_handler.py +++ /dev/null @@ -1,23 +0,0 @@ -# backend/core/directory_handler.py -import os -from pathlib import Path -import tkinter as tk -from tkinter import filedialog -from flask import jsonify - -def select_directory(): - """Show directory selection dialog and return selected path""" - root = tk.Tk() - root.withdraw() - root.attributes('-topmost', True) # Hace que el diálogo siempre esté encima - - try: - directory = filedialog.askdirectory( - title="Select Work Directory", - initialdir=os.path.expanduser("~") - ) - return {"path": directory} if directory else {"error": "No directory selected"} - except Exception as e: - return {"error": str(e)} - finally: - root.destroy() \ No newline at end of file diff --git a/claude/group_settings_manager.py b/claude/group_settings_manager.py deleted file mode 100644 index 4e04cc8..0000000 --- a/claude/group_settings_manager.py +++ /dev/null @@ -1,122 +0,0 @@ -# backend/core/group_settings_manager.py -from pathlib import Path -import json -from typing import Dict, Any -from datetime import datetime -import os - - -class GroupSettingsManager: - """Manages settings for script groups""" - - def __init__(self, script_groups_dir: Path): - self.script_groups_dir = script_groups_dir - self.config_schema = self._load_config_schema() - - def _load_config_schema(self) -> Dict[str, Any]: - """Load the main configuration schema for script groups""" - schema_file = self.script_groups_dir / "config.json" - try: - with open(schema_file, "r", encoding="utf-8") as f: - return json.load(f) - except Exception as e: - print(f"Error loading group config schema: {e}") - return { - "config_schema": { - "work_dir": { - "type": "string", - "description": "Working directory for this script group", - "required": True, - }, - "description": { - "type": "string", - "description": "Description of this script group", - "default": "", - }, - } - } - - def _validate_setting( - self, key: str, value: Any, field_schema: Dict[str, Any] - ) -> Any: - """Validate and convert a single setting value""" - field_type = field_schema.get("type") - - if value is None or value == "": - if field_schema.get("required", False): - raise ValueError(f"Field '{key}' is required") - return field_schema.get("default") - - try: - if field_type == "string": - return str(value) - elif field_type == "number": - return float(value) if "." in str(value) else int(value) - elif field_type == "boolean": - if isinstance(value, str): - return value.lower() == "true" - return bool(value) - elif field_type == "directory": - path = Path(value) - if not path.is_absolute(): - path = Path(self.script_groups_dir) / path - if not path.exists(): - path.mkdir(parents=True, exist_ok=True) - return str(path) - else: - return value - except Exception as e: - raise ValueError(f"Invalid value for field '{key}': {str(e)}") - - def get_group_settings(self, group_id: str) -> Dict[str, Any]: - """Get settings for a specific script group""" - settings_file = self.script_groups_dir / group_id / "group.json" - - if settings_file.exists(): - try: - with open(settings_file, "r", encoding="utf-8") as f: - return json.load(f) - except Exception as e: - print(f"Error loading group settings: {e}") - - return { - "work_dir": "", - "description": "", - "created_at": datetime.now().isoformat(), - "updated_at": datetime.now().isoformat(), - } - - def update_group_settings(self, group_id: str, settings: Dict[str, Any]): - """Update settings for a specific script group""" - schema = self.config_schema.get("config_schema", {}) - validated_settings = {} - - # Validate each setting against schema - for key, field_schema in schema.items(): - if key in settings: - validated_settings[key] = self._validate_setting( - key, settings[key], field_schema - ) - elif field_schema.get("required", False): - raise ValueError(f"Required field '{key}' is missing") - else: - validated_settings[key] = field_schema.get("default") - - # Add non-schema fields - for key, value in settings.items(): - if key not in schema: - validated_settings[key] = value - - # Update timestamps - validated_settings["updated_at"] = datetime.now().isoformat() - - group_dir = self.script_groups_dir / group_id - settings_file = group_dir / "group.json" - - if not settings_file.exists(): - validated_settings["created_at"] = validated_settings["updated_at"] - group_dir.mkdir(parents=True, exist_ok=True) - - # Save settings - with open(settings_file, "w", encoding="utf-8") as f: - json.dump(validated_settings, f, indent=4) diff --git a/claude/index.html b/claude/index.html deleted file mode 100644 index 0912f1a..0000000 --- a/claude/index.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - Local Scripts Web - - - - - - - -
- - - - -
-
-
- -
-
-

Work Directory

-
- - -
-
-
- - -
-
-

Scripts

-
- - -
-
-
- - -
-
-
-

Output

- -
-
-
-
-
-
-
-
-
- - - - - - - - - - - \ No newline at end of file diff --git a/claude/main.js b/claude/main.js deleted file mode 100644 index 951ab99..0000000 --- a/claude/main.js +++ /dev/null @@ -1,264 +0,0 @@ -// frontend/static/js/main.js - -// Global state -let currentProfile = null; - -// Definir clases comunes para inputs -const STYLES = { - editableInput: "mt-1 block w-full rounded-md border-2 border-gray-300 bg-green-50 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500", - readonlyInput: "mt-1 block w-full rounded-md border-2 border-gray-200 bg-gray-100 px-3 py-2 shadow-sm", - button: "px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600", - buttonSecondary: "px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300" -}; - -async function initializeApp() { - try { - console.log('Inicializando aplicación...'); - - // Cargar perfiles - const profiles = await apiRequest('/profiles'); - console.log('Profiles loaded:', profiles); - - // Obtener último perfil usado - const lastProfileId = localStorage.getItem('lastProfileId') || 'default'; - console.log('Last profile ID:', lastProfileId); - - // Actualizar selector de perfiles - updateProfileSelector(profiles); - - // Seleccionar el último perfil usado - const selectedProfile = profiles.find(p => p.id === lastProfileId) || profiles[0]; - if (selectedProfile) { - console.log('Selecting profile:', selectedProfile.id); - await selectProfile(selectedProfile.id); - } - - // Cargar grupos de scripts y restaurar la última selección - await restoreScriptGroup(); - - // Actualizar la interfaz - updateWorkDirDisplay(); - - } catch (error) { - console.error('Error al inicializar la aplicación:', error); - showError('Error al inicializar la aplicación'); - } -} - -async function restoreScriptGroup() { - try { - // Primero cargar los grupos disponibles - await loadScriptGroups(); - - // Luego intentar restaurar el último grupo seleccionado - const lastGroupId = localStorage.getItem('lastGroupId'); - if (lastGroupId) { - console.log('Restoring last group:', lastGroupId); - const groupSelect = document.getElementById('groupSelect'); - if (groupSelect) { - groupSelect.value = lastGroupId; - if (groupSelect.value) { // Verifica que el valor se haya establecido correctamente - await loadGroupScripts(lastGroupId); - } else { - console.log('Selected group no longer exists:', lastGroupId); - localStorage.removeItem('lastGroupId'); - } - } - } - } catch (error) { - console.error('Error restoring script group:', error); - } -} - -// Función para restaurar el último estado -async function restoreLastState() { - const lastProfileId = localStorage.getItem('lastProfileId'); - const lastGroupId = localStorage.getItem('lastGroupId'); - - console.log('Restoring last state:', { lastProfileId, lastGroupId }); - - if (lastProfileId) { - const profileSelect = document.getElementById('profileSelect'); - profileSelect.value = lastProfileId; - await selectProfile(lastProfileId); - } - - if (lastGroupId) { - const groupSelect = document.getElementById('groupSelect'); - if (groupSelect) { - groupSelect.value = lastGroupId; - await loadGroupScripts(lastGroupId); - } - } -} - -// API functions -async function apiRequest(endpoint, options = {}) { - try { - const response = await fetch(`/api${endpoint}`, { - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } - }); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.error || 'Error en la solicitud API'); - } - - return await response.json(); - } catch (error) { - console.error('Error API:', error); - showError(error.message); - throw error; - } -} - -async function loadProfiles() { - try { - const profiles = await apiRequest('/profiles'); - updateProfileSelector(profiles); - - // Obtener último perfil usado - const lastProfileId = localStorage.getItem('lastProfileId'); - - // Seleccionar perfil guardado o el default - const defaultProfile = profiles.find(p => p.id === (lastProfileId || 'default')) || profiles[0]; - if (defaultProfile) { - await selectProfile(defaultProfile.id); - } - } catch (error) { - showError('Error al cargar los perfiles'); - } -} - -async function selectProfile(profileId) { - try { - console.log('Seleccionando perfil:', profileId); - currentProfile = await apiRequest(`/profiles/${profileId}`); - - // Guardar en localStorage - localStorage.setItem('lastProfileId', profileId); - console.log('Profile ID saved to storage:', profileId); - - // Actualizar explícitamente el valor del combo - const select = document.getElementById('profileSelect'); - if (select) { - select.value = profileId; - console.log('Updated profileSelect value to:', profileId); - } - - updateWorkDirDisplay(); - - // Recargar scripts con el último grupo seleccionado - await restoreScriptGroup(); - } catch (error) { - console.error('Error al seleccionar perfil:', error); - showError('Error al cargar el perfil'); - } -} - -// Initialize when page loads -document.addEventListener('DOMContentLoaded', initializeApp); - -function updateProfileSelector(profiles) { - const select = document.getElementById('profileSelect'); - const lastProfileId = localStorage.getItem('lastProfileId') || 'default'; - - console.log('Updating profile selector. Last profile ID:', lastProfileId); - - // Construir las opciones - select.innerHTML = profiles.map(profile => ` - - `).join(''); - - // Asegurar que el valor seleccionado sea correcto - select.value = lastProfileId; - console.log('Set profileSelect value to:', lastProfileId); -} - -async function changeProfile() { - const select = document.getElementById('profileSelect'); - if (select.value) { - await selectProfile(select.value); - await loadScriptGroups(); // Reload scripts when profile changes - } -} - -// Work directory functions -function updateWorkDirDisplay() { - const input = document.getElementById('workDirPath'); - if (input && currentProfile) { - input.value = currentProfile.work_dir || ''; - } -} - -async function selectWorkDir() { - try { - console.log('Requesting directory selection...'); // Debug - const response = await apiRequest('/select-directory'); - console.log('Directory selection response:', response); // Debug - - if (response.path) { - console.log('Updating profile with new work_dir:', response.path); // Debug - const updateResponse = await apiRequest(`/profiles/${currentProfile.id}`, { - method: 'PUT', - body: JSON.stringify({ - ...currentProfile, - work_dir: response.path - }) - }); - console.log('Profile update response:', updateResponse); // Debug - - await selectProfile(currentProfile.id); - showSuccess('Directorio de trabajo actualizado correctamente'); - } - } catch (error) { - console.error('Error al seleccionar directorio:', error); // Debug - showError('Error al actualizar el directorio de trabajo'); - } -} - -// Output functions -function showError(message) { - const output = document.getElementById('outputArea'); - const timestamp = new Date().toLocaleTimeString(); - output.innerHTML += `\n[${timestamp}] ERROR: ${message}`; - output.scrollTop = output.scrollHeight; -} - -function showSuccess(message) { - const output = document.getElementById('outputArea'); - const timestamp = new Date().toLocaleTimeString(); - output.innerHTML += `\n[${timestamp}] SUCCESS: ${message}`; - output.scrollTop = output.scrollHeight; -} - -function clearOutput() { - const output = document.getElementById('outputArea'); - output.innerHTML = ''; -} - -// Modal helper functions -function closeModal(button) { - const modal = button.closest('.modal'); - if (modal) { - modal.remove(); - } -} - -// Global error handler -window.addEventListener('unhandledrejection', function(event) { - console.error('Unhandled promise rejection:', event.reason); - showError('An unexpected error occurred'); -}); - -// Export functions for use in other modules -window.showError = showError; -window.showSuccess = showSuccess; -window.closeModal = closeModal; -window.currentProfile = currentProfile; \ No newline at end of file diff --git a/claude/modal.js b/claude/modal.js deleted file mode 100644 index 4e17e74..0000000 --- a/claude/modal.js +++ /dev/null @@ -1,39 +0,0 @@ -// frontend/static/js/modal.js -// static/js/modal.js -function createModal(title, content, onSave = null) { - const modal = document.createElement('div'); - modal.className = 'fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50'; - - modal.innerHTML = ` -
-
-

${title}

-
-
- ${content} -
-
- - ${onSave ? ` - - ` : ''} -
-
- `; - - document.body.appendChild(modal); - return modal; -} - -function closeModal(button) { - const modal = button.closest('.fixed'); - if (modal) { - modal.remove(); - } -} \ No newline at end of file diff --git a/claude/profile.js b/claude/profile.js deleted file mode 100644 index 4904f92..0000000 --- a/claude/profile.js +++ /dev/null @@ -1,324 +0,0 @@ -// frontend/static/js/profile.js -let selectedProfileId = localStorage.getItem('selectedProfileId') || 'default'; -let editingProfile = null; - -// Profile functions -async function loadProfiles() { - try { - const response = await apiRequest('/profiles'); - const profiles = Object.values(response); - - // Actualizar el selector manteniendo el valor seleccionado - const select = document.getElementById('profileSelect'); - select.innerHTML = profiles.map(profile => ` - - `).join(''); - - // Establecer el valor seleccionado después de actualizar las opciones - if (response[selectedProfileId]) { - select.value = selectedProfileId; - await selectProfile(selectedProfileId); - } else { - selectedProfileId = 'default'; - select.value = 'default'; - await selectProfile('default'); - } - - // Asegurarse de que el evento change no sobrescriba la selección - select.addEventListener('change', onProfileChange, { once: true }); - - } catch (error) { - showError('Error al cargar los perfiles'); - } -} - -async function selectProfile(profileId) { - try { - currentProfile = await apiRequest(`/profiles/${profileId}`); - updateWorkDirDisplay(); - } catch (error) { - showError('Failed to load profile'); - } -} - -async function changeProfile() { - const select = document.getElementById('profileSelect'); - await selectProfile(select.value); -} - -async function selectWorkDir() { - try { - const response = await apiRequest('/select-directory'); - if (response.path) { - await apiRequest(`/profiles/${currentProfile.id}`, { - method: 'PUT', - body: JSON.stringify({ - ...currentProfile, - work_dir: response.path - }) - }); - await selectProfile(currentProfile.id); - showSuccess('Work directory updated successfully'); - } - } catch (error) { - showError('Failed to update work directory'); - } -} - -// Profile editor modal - -function showProfileEditor(profile = null) { - editingProfile = profile; - const modal = document.createElement('div'); - modal.className = 'modal active'; - - const editableInputClass = "mt-1 block w-full rounded-md border-2 border-gray-300 bg-green-50 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"; - const readonlyInputClass = "mt-1 block w-full rounded-md border-2 border-gray-200 bg-gray-100 px-3 py-2 shadow-sm"; - - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); -} - -async function saveProfile(event) { - event.preventDefault(); - const form = event.target; - const formData = new FormData(form); - - const profileData = { - id: formData.get('id'), - name: formData.get('name'), - work_dir: formData.get('work_dir'), - llm_settings: { - model: formData.get('llm_model'), - api_key: formData.get('api_key'), - temperature: parseFloat(formData.get('temperature')) - } - }; - - try { - if (editingProfile) { - await apiRequest(`/profiles/${editingProfile.id}`, { - method: 'PUT', - body: JSON.stringify(profileData) - }); - } else { - await apiRequest('/profiles', { - method: 'POST', - body: JSON.stringify(profileData) - }); - } - - await loadProfiles(); - closeModal(event.target); - showSuccess(`Perfil ${editingProfile ? 'actualizado' : 'creado'} correctamente`); - } catch (error) { - showError(`Error al ${editingProfile ? 'actualizar' : 'crear'} el perfil`); - } -} - -// static/js/profile.js -async function editProfile() { - if (!currentProfile) { - showError('No profile selected'); - return; - } - - const content = ` -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- `; - - const modal = createModal('Edit Profile', content, true); - modal.querySelector('[onclick="saveModal(this)"]').onclick = async () => { - await saveProfile(modal); - }; -} - -async function saveProfile(modal) { - const form = modal.querySelector('#profileForm'); - const formData = new FormData(form); - - const profileData = { - id: formData.get('id'), - name: formData.get('name'), - work_dir: formData.get('work_dir'), - llm_settings: { - model: formData.get('llm_model'), - api_key: formData.get('api_key'), - temperature: parseFloat(formData.get('temperature')) - } - }; - - try { - if (editingProfile) { - await apiRequest(`/profiles/${editingProfile.id}`, { - method: 'PUT', - body: JSON.stringify(profileData) - }); - } else { - await apiRequest('/profiles', { - method: 'POST', - body: JSON.stringify(profileData) - }); - } - - await loadProfiles(); - closeModal(modal); - showSuccess(`Perfil ${editingProfile ? 'actualizado' : 'creado'} correctamente`); - } catch (error) { - showError(`Error al ${editingProfile ? 'actualizar' : 'crear'} el perfil`); - } -} - -function newProfile() { - const editableInputClass = "mt-1 block w-full rounded-md border-2 border-gray-300 bg-green-50 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"; - const readonlyInputClass = "mt-1 block w-full rounded-md border-2 border-gray-200 bg-gray-100 px-3 py-2 shadow-sm"; - - const content = ` -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- `; - - const modal = createModal('New Profile', content, true); - editingProfile = null; - - modal.querySelector('[onclick="saveModal(this)"]').onclick = async () => { - await saveProfile(modal); - }; -} - -async function onProfileChange(event) { - const newProfileId = event.target.value; - if (newProfileId !== selectedProfileId) { - selectedProfileId = newProfileId; - localStorage.setItem('selectedProfileId', selectedProfileId); - await selectProfile(selectedProfileId); - } -} \ No newline at end of file diff --git a/claude/profile_manager.py b/claude/profile_manager.py deleted file mode 100644 index 3baa562..0000000 --- a/claude/profile_manager.py +++ /dev/null @@ -1,133 +0,0 @@ -# backend/core/profile_manager.py -from pathlib import Path -import json -from typing import Dict, Any, List, Optional -from datetime import datetime - - -class ProfileManager: - """Manages configuration profiles""" - - DEFAULT_PROFILE = { - "id": "default", - "name": "Default Profile", - "llm_settings": {"model": "gpt-4", "temperature": 0.7, "api_key": ""}, - "created_at": "", - "updated_at": "", - } - - def __init__(self, data_dir: Path): - self.data_dir = data_dir - self.profiles_file = data_dir / "profiles.json" - self.profiles: Dict[str, Dict] = self._load_profiles() - - def _load_profiles(self) -> Dict[str, Dict]: - """Load profiles from file""" - if self.profiles_file.exists(): - try: - with open(self.profiles_file, "r", encoding="utf-8") as f: - profiles = json.load(f) - # Ensure default profile exists - if "default" not in profiles: - profiles["default"] = self._create_default_profile() - return profiles - except Exception as e: - print(f"Error loading profiles: {e}") - return {"default": self._create_default_profile()} - else: - # Create directory if it doesn't exist - self.profiles_file.parent.mkdir(parents=True, exist_ok=True) - # Create default profile - profiles = {"default": self._create_default_profile()} - self._save_profiles(profiles) - return profiles - - def _create_default_profile(self) -> Dict[str, Any]: - """Create default profile with timestamp""" - profile = self.DEFAULT_PROFILE.copy() - now = datetime.now().isoformat() - profile["created_at"] = now - profile["updated_at"] = now - return profile - - def _save_profiles(self, profiles: Optional[Dict] = None): - """Save profiles to file""" - if profiles is None: - profiles = self.profiles - try: - print(f"Saving profiles to: {self.profiles_file}") # Agregar debug - with open(self.profiles_file, "w", encoding="utf-8") as f: - json.dump(profiles, f, indent=4) - print("Profiles saved successfully") # Agregar debug - except Exception as e: - print(f"Error saving profiles: {e}") # Agregar debug - raise # Re-lanzar la excepción para que se maneje arriba - - def get_all_profiles(self) -> List[Dict[str, Any]]: - """Get all profiles""" - return list(self.profiles.values()) - - def get_profile(self, profile_id: str) -> Optional[Dict[str, Any]]: - """Get specific profile""" - return self.profiles.get(profile_id) - - def create_profile(self, profile_data: Dict[str, Any]) -> Dict[str, Any]: - """Create new profile""" - if "id" not in profile_data: - raise ValueError("Profile must have an id") - - profile_id = profile_data["id"] - if profile_id in self.profiles: - raise ValueError(f"Profile {profile_id} already exists") - - # Add timestamps - now = datetime.now().isoformat() - profile_data["created_at"] = now - profile_data["updated_at"] = now - - # Ensure required fields - for key in ["name", "llm_settings"]: - if key not in profile_data: - profile_data[key] = self.DEFAULT_PROFILE[key] - - self.profiles[profile_id] = profile_data - self._save_profiles() - return profile_data - - def update_profile( - self, profile_id: str, profile_data: Dict[str, Any] - ) -> Dict[str, Any]: - """Update existing profile""" - try: - print(f"Updating profile {profile_id} with data: {profile_data}") - if profile_id not in self.profiles: - raise ValueError(f"Profile {profile_id} not found") - - if profile_id == "default" and "id" in profile_data: - raise ValueError("Cannot change id of default profile") - - # Update timestamp - profile_data["updated_at"] = datetime.now().isoformat() - - # Update profile - current_profile = self.profiles[profile_id].copy() # Hacer una copia - current_profile.update(profile_data) # Actualizar la copia - self.profiles[profile_id] = current_profile # Asignar la copia actualizada - - print(f"Updated profile: {self.profiles[profile_id]}") - self._save_profiles() - return self.profiles[profile_id] - except Exception as e: - print(f"Error in update_profile: {e}") # Agregar debug - raise - - def delete_profile(self, profile_id: str): - """Delete profile""" - if profile_id == "default": - raise ValueError("Cannot delete default profile") - - if profile_id not in self.profiles: - raise ValueError(f"Profile {profile_id} not found") - - del self.profiles[profile_id] - self._save_profiles() diff --git a/claude/profiles.json b/claude/profiles.json deleted file mode 100644 index 3b68f75..0000000 --- a/claude/profiles.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "default": { - "id": "default", - "name": "Default Profile", - "work_dir": "", - "llm_settings": { - "model": "gpt-4", - "temperature": 0.7, - "api_key": "" - }, - "created_at": "2025-02-07T12:47:49.766608", - "updated_at": "2025-02-07T12:47:49.766608" - }, - "1": { - "id": "1", - "name": "Base", - "work_dir": "C:/Estudio", - "llm_settings": { - "api_key": "333333333333", - "model": "gpt-4", - "temperature": 0.7 - }, - "created_at": "2025-02-07T13:00:43.541932", - "updated_at": "2025-02-07T23:34:43.039269" - } -} \ No newline at end of file diff --git a/claude/project_structure.txt b/claude/project_structure.txt deleted file mode 100644 index 46cb7fe..0000000 --- a/claude/project_structure.txt +++ /dev/null @@ -1,39 +0,0 @@ -Estructura del proyecto original: - -└── LocalScriptsWeb - ├── backend - │ ├── core - │ │ ├── __init__.py - │ │ ├── directory_handler.py - │ │ ├── group_settings_manager.py - │ │ ├── profile_manager.py - │ │ ├── script_manager.py - │ │ └── workdir_config.py - │ ├── script_groups - │ │ ├── example_group - │ │ │ ├── __init__.py - │ │ │ ├── config.json - │ │ │ ├── x1.py - │ │ │ └── x2.py - │ │ ├── __init__.py - │ │ ├── base_script.py - │ │ └── config.json - │ ├── __init__.py - │ └── app.py - ├── data - │ └── profiles.json - ├── frontend - │ ├── static - │ │ ├── css - │ │ │ └── style.css - │ │ └── js - │ │ ├── main.js - │ │ ├── modal.js - │ │ ├── profile.js - │ │ ├── scripts.js - │ │ └── workdir_config.js - │ └── templates - │ ├── base.html - │ └── index.html - ├── claude_file_organizer.py - └── files.txt \ No newline at end of file diff --git a/claude/script_manager.py b/claude/script_manager.py deleted file mode 100644 index 03d83a6..0000000 --- a/claude/script_manager.py +++ /dev/null @@ -1,189 +0,0 @@ -# backend/core/script_manager.py -from pathlib import Path -import importlib.util -import inspect -from typing import Dict, List, Any, Optional -import json -from .group_settings_manager import GroupSettingsManager # Agregar esta importación - - -class ScriptManager: - def __init__(self, script_groups_dir: Path): - self.script_groups_dir = script_groups_dir - self.group_settings = GroupSettingsManager(script_groups_dir) - - def get_group_settings(self, group_id: str) -> Dict[str, Any]: - """Get settings for a script group""" - return self.group_settings.get_group_settings(group_id) - - def update_group_settings(self, group_id: str, settings: Dict[str, Any]): - """Update settings for a script group""" - return self.group_settings.update_group_settings(group_id, settings) - - def get_group_config_schema(self, group_id: str) -> Dict[str, Any]: - """Get configuration schema for a script group""" - config_file = self.script_groups_dir / group_id / "config.json" - print(f"Looking for config file: {config_file}") # Debug - - if config_file.exists(): - try: - with open(config_file, "r", encoding="utf-8") as f: - schema = json.load(f) - print(f"Loaded schema: {schema}") # Debug - return schema - except Exception as e: - print(f"Error loading group config schema: {e}") # Debug - else: - print(f"Config file not found: {config_file}") # Debug - - # Retornar un schema vacío si no existe el archivo - return {"group_name": group_id, "description": "", "config_schema": {}} - - def get_available_groups(self) -> List[Dict[str, Any]]: - """Get list of available script groups""" - groups = [] - - for group_dir in self.script_groups_dir.iterdir(): - if group_dir.is_dir() and not group_dir.name.startswith("_"): - groups.append( - { - "id": group_dir.name, - "name": group_dir.name.replace("_", " ").title(), - "path": str(group_dir), - } - ) - - return groups - - def get_group_scripts(self, group_id: str) -> List[Dict[str, Any]]: - """Get scripts for a specific group""" - group_dir = self.script_groups_dir / group_id - print(f"Looking for scripts in: {group_dir}") # Debug - - if not group_dir.exists() or not group_dir.is_dir(): - print(f"Directory not found: {group_dir}") # Debug - raise ValueError(f"Script group '{group_id}' not found") - - scripts = [] - for script_file in group_dir.glob("x[0-9].py"): - print(f"Found script file: {script_file}") # Debug - script_info = self._analyze_script(script_file) - if script_info: - scripts.append(script_info) - - return sorted(scripts, key=lambda x: x["id"]) - - def discover_groups(self) -> List[Dict[str, Any]]: - """Discover all script groups""" - groups = [] - - for group_dir in self.script_groups_dir.iterdir(): - if group_dir.is_dir() and not group_dir.name.startswith("_"): - group_info = self._analyze_group(group_dir) - if group_info: - groups.append(group_info) - - return groups - - def _analyze_group(self, group_dir: Path) -> Optional[Dict[str, Any]]: - """Analyze a script group directory""" - scripts = [] - - for script_file in group_dir.glob("x[0-9].py"): - try: - script_info = self._analyze_script(script_file) - if script_info: - scripts.append(script_info) - except Exception as e: - print(f"Error analyzing script {script_file}: {e}") - - if scripts: - return { - "id": group_dir.name, - "name": group_dir.name.replace("_", " ").title(), - "scripts": sorted(scripts, key=lambda x: x["id"]), - } - return None - - def _analyze_script(self, script_file: Path) -> Optional[Dict[str, Any]]: - """Analyze a single script file""" - try: - # Import script module - spec = importlib.util.spec_from_file_location(script_file.stem, script_file) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - # Find script class - script_class = None - for name, obj in inspect.getmembers(module): - if ( - inspect.isclass(obj) - and obj.__module__ == module.__name__ - and hasattr(obj, "run") - ): - script_class = obj - break - - if script_class: - # Extraer la primera línea del docstring como nombre - docstring = inspect.getdoc(script_class) - if docstring: - name, *description = docstring.split("\n", 1) - description = description[0] if description else "" - else: - name = script_file.stem - description = "" - - return { - "id": script_file.stem, - "name": name.strip(), - "description": description.strip(), - "file": str(script_file.relative_to(self.script_groups_dir)), - } - - except Exception as e: - print(f"Error loading script {script_file}: {e}") - - return None - - def execute_script( - self, group_id: str, script_id: str, profile: Dict[str, Any] - ) -> Dict[str, Any]: - """Execute a specific script""" - # Get group settings first - group_settings = self.group_settings.get_group_settings(group_id) - work_dir = group_settings.get("work_dir") - - if not work_dir: - raise ValueError(f"No work directory configured for group {group_id}") - - script_file = self.script_groups_dir / group_id / f"{script_id}.py" - - if not script_file.exists(): - raise ValueError(f"Script {script_id} not found in group {group_id}") - - try: - # Import script module - spec = importlib.util.spec_from_file_location(script_id, script_file) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - # Find and instantiate script class - script_class = None - for name, obj in inspect.getmembers(module): - if ( - inspect.isclass(obj) - and obj.__module__ == module.__name__ - and hasattr(obj, "run") - ): - script_class = obj - break - - if not script_class: - raise ValueError(f"No valid script class found in {script_id}") - - script = script_class() - return script.run(work_dir, profile) - - except Exception as e: - return {"status": "error", "error": str(e)} diff --git a/claude/scripts.js b/claude/scripts.js deleted file mode 100644 index 4f85782..0000000 --- a/claude/scripts.js +++ /dev/null @@ -1,815 +0,0 @@ -// frontend/static/js/scripts.js - -// Script groups state -let scriptGroups = []; - -// Load script groups when page loads -document.addEventListener('DOMContentLoaded', async () => { - await loadScriptGroups(); -}); - -// Load script groups when page loads -document.addEventListener('DOMContentLoaded', async () => { - await loadScriptGroups(); -}); - -async function loadScriptGroups() { - try { - // Obtener los grupos desde el servidor - const groups = await apiRequest('/script-groups'); - console.log('Loaded script groups:', groups); - - // Obtener el selector y el último grupo seleccionado - const select = document.getElementById('groupSelect'); - const lastGroupId = localStorage.getItem('lastGroupId'); - console.log('Last group ID:', lastGroupId); - - // Remover event listener anterior si existe - select.removeEventListener('change', handleGroupChange); - - // Construir las opciones - select.innerHTML = ` - - ${groups.map(group => ` - - `).join('')} - `; - - // Agregar event listener para cambios - select.addEventListener('change', handleGroupChange); - console.log('Added change event listener to groupSelect'); - - // Si hay un grupo guardado, cargarlo - if (lastGroupId) { - console.log('Loading last group scripts:', lastGroupId); - await loadGroupScripts(lastGroupId); - } - - } catch (error) { - console.error('Error al cargar grupos de scripts:', error); - showError('Error al cargar grupos de scripts'); - } -} - - -// Función para manejar el cambio de grupo -async function handleGroupChange(event) { - const groupId = event.target.value; - console.log('Group selection changed:', groupId); - - if (groupId) { - localStorage.setItem('lastGroupId', groupId); - console.log('Saved lastGroupId:', groupId); - } else { - localStorage.removeItem('lastGroupId'); - console.log('Removed lastGroupId'); - } - - await loadGroupScripts(groupId); -} - -// Actualizar función de cambio de perfil para mantener la persistencia -async function changeProfile() { - const select = document.getElementById('profileSelect'); - if (select.value) { - await selectProfile(select.value); - localStorage.setItem('lastProfileId', select.value); - - // Al cambiar de perfil, intentamos mantener el último grupo seleccionado - const lastGroupId = localStorage.getItem('lastGroupId'); - if (lastGroupId) { - const groupSelect = document.getElementById('groupSelect'); - if (groupSelect) { - groupSelect.value = lastGroupId; - await loadGroupScripts(lastGroupId); - } - } - } -} - -async function loadGroupScripts(groupId) { - const scriptList = document.getElementById('scriptList'); - - if (!groupId) { - scriptList.style.display = 'none'; - localStorage.removeItem('lastGroupId'); // Limpiar selección - return; - } - - // Guardar grupo seleccionado - localStorage.setItem('lastGroupId', groupId); - console.log('Group saved:', groupId); - - if (!currentProfile?.work_dir) { - scriptList.innerHTML = ` -
-
-
-

- Por favor, seleccione primero un directorio de trabajo -

-
-
-
- `; - scriptList.style.display = 'block'; - return; - } - - try { - console.log('Loading data for group:', groupId); - - // Actualizar el selector para reflejar la selección actual - const groupSelect = document.getElementById('groupSelect'); - if (groupSelect && groupSelect.value !== groupId) { - groupSelect.value = groupId; - } - - // Cargar y loguear scripts - let groupScripts, configSchema; - try { - groupScripts = await apiRequest(`/script-groups/${groupId}/scripts`); - console.log('Scripts loaded:', groupScripts); - } catch (e) { - console.error('Error loading scripts:', e); - throw e; - } - - try { - configSchema = await apiRequest(`/script-groups/${groupId}/config-schema`); - console.log('Config schema loaded:', configSchema); - } catch (e) { - console.error('Error loading config schema:', e); - throw e; - } - - // Intentar cargar configuración actual - let currentConfig = {}; - try { - currentConfig = await apiRequest( - `/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}` - ); - console.log('Current config loaded:', currentConfig); - } catch (e) { - console.warn('No existing configuration found, using defaults'); - } - - // Verificar que tenemos los datos necesarios - if (!groupScripts || !configSchema) { - throw new Error('Failed to load required data'); - } - - console.log('Rendering UI with:', { - groupScripts, - configSchema, - currentConfig - }); - - scriptList.innerHTML = ` - -
-
-
-

- ${configSchema.group_name || 'Configuración'} -

-

${configSchema.description || ''}

-
- -
-
-
- ${Object.entries(configSchema.config_schema || {}).map(([key, field]) => ` -
- - ${generateFormField(key, field, currentConfig[key])} -
- `).join('')} -
- -
-
-
-
- - -
- ${groupScripts.map(script => ` -
-
-
-

${script.name || script.id}

-

${script.description || 'Sin descripción disponible'}

-
- -
-
- `).join('')} -
`; - - scriptList.style.display = 'block'; - - // Agregar evento para guardar configuración - const form = document.getElementById('groupConfigForm'); - form.addEventListener('submit', async (e) => { - e.preventDefault(); - await saveGroupConfig(groupId, form); - }); - - } catch (error) { - console.error('Error in loadGroupScripts:', error); - showError('Failed to load scripts and configuration'); - } -} - -// Update script groups display -function updateScriptGroupsDisplay() { - const container = document.getElementById('scriptGroups'); - - if (!scriptGroups.length) { - container.innerHTML = '

No script groups available

'; - return; - } - - container.innerHTML = scriptGroups.map(group => ` -
-
-

${group.name}

- -
-
- ${group.scripts.map(script => ` -
-
-

${script.name}

-

${script.description || 'No description available'}

-
-
- -
-
- `).join('')} -
-
- `).join(''); -} - -// Run a script -async function runScript(groupId, scriptId) { - if (!currentProfile?.work_dir) { - showError('Please select a work directory first'); - return; - } - - try { - const result = await apiRequest(`/scripts/${groupId}/${scriptId}/run`, { - method: 'POST', - body: JSON.stringify({ - work_dir: currentProfile.work_dir, - profile: currentProfile - }) - }); - - if (result.status === 'error') { - showError(result.error); - } else { - showSuccess(`Script ${scriptId} executed successfully`); - if (result.output) { - const output = document.getElementById('outputArea'); - output.innerHTML += `\n[${new Date().toLocaleTimeString()}] ${result.output}`; - output.scrollTop = output.scrollHeight; - } - } - } catch (error) { - showError(`Failed to run script: ${error.message}`); - } -} - -// Configure script group -async function configureGroup(groupId) { - if (!currentProfile?.work_dir) { - showError('Please select a work directory first'); - return; - } - - try { - const config = await getGroupConfig(groupId); - showGroupConfigEditor(groupId, config); - } catch (error) { - showError('Failed to load group configuration'); - } -} - -// Show group configuration editor -function showGroupConfigEditor(groupId, config) { - const group = scriptGroups.find(g => g.id === groupId); - if (!group) return; - - const modal = document.createElement('div'); - modal.className = 'modal active'; - - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); -} - -function generateFormField(key, field, currentValue) { - switch (field.type) { - case 'string': - return ` - - `; - case 'number': - return ` - - `; - case 'boolean': - return ` - - `; - case 'select': - return ` - - `; - default: - return ``; - } -} - -async function loadGroupScripts(groupId) { - const scriptList = document.getElementById('scriptList'); - - if (!groupId) { - scriptList.style.display = 'none'; - localStorage.removeItem('lastGroupId'); - return; - } - - if (!currentProfile?.work_dir) { - scriptList.innerHTML = ` -
-
-
-

- Por favor, seleccione primero un directorio de trabajo -

-
-
-
- `; - scriptList.style.display = 'block'; - return; - } - - try { - console.log('Loading data for group:', groupId); - - // Cargar y loguear scripts - let groupScripts, configSchema; - try { - groupScripts = await apiRequest(`/script-groups/${groupId}/scripts`); - console.log('Scripts loaded:', groupScripts); - } catch (e) { - console.error('Error loading scripts:', e); - throw e; - } - - try { - configSchema = await apiRequest(`/script-groups/${groupId}/config-schema`); - console.log('Config schema loaded:', configSchema); - } catch (e) { - console.error('Error loading config schema:', e); - throw e; - } - - // Intentar cargar configuración actual - let currentConfig = {}; - try { - console.log('Loading current config for work_dir:', currentProfile.work_dir); - currentConfig = await apiRequest( - `/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}` - ); - console.log('Current config loaded:', currentConfig); - } catch (e) { - console.warn('No existing configuration found, using defaults'); - } - - // Verificar que tenemos los datos necesarios - if (!groupScripts || !configSchema) { - throw new Error('Failed to load required data'); - } - - console.log('Rendering UI with:', { - groupScripts, - configSchema, - currentConfig - }); - - scriptList.innerHTML = ` - -
-
-
-

- ${configSchema.group_name || 'Configuración'} -

-

${configSchema.description || ''}

-
- -
-
-
- ${Object.entries(configSchema.config_schema || {}).map(([key, field]) => ` -
- - ${generateFormField(key, field, currentConfig[key])} -
- `).join('')} -
- -
-
-
-
- - -
- ${groupScripts.map(script => ` -
-
-
-

${script.name || script.id}

-

${script.description || 'Sin descripción disponible'}

-
- -
-
- `).join('')} -
`; - - scriptList.style.display = 'block'; - - // Agregar evento para guardar configuración - const form = document.getElementById('groupConfigForm'); - form.addEventListener('submit', async (e) => { - e.preventDefault(); - await saveGroupConfig(groupId, form); - }); - - } catch (error) { - console.error('Error in loadGroupScripts:', error); - showError('Failed to load scripts and configuration'); - } -} - -async function editConfigSchema(groupId) { - try { - const schema = await apiRequest(`/script-groups/${groupId}/config-schema`); - const configSection = document.createElement('div'); - configSection.id = 'schemaEditor'; - configSection.className = 'mb-6 bg-white shadow sm:rounded-lg'; - - configSection.innerHTML = ` -
-

Editar Configuración del Esquema

- -
-
-
-
-
- - -
-
- -
-
-
- - -
-
-
-

Parámetros

-
- ${Object.entries(schema.config_schema).map(([key, param]) => ` -
- -
-
- - -
-
- - -
-
- - -
-
- - -
- ${param.type === 'select' ? ` -
- - -
- ` : ''} -
-
- `).join('')} -
-
- - -
-
-
- `; - - // Insertamos el editor justo después del botón "Edit Schema" - const scriptList = document.getElementById('scriptList'); - const existingEditor = document.getElementById('schemaEditor'); - if (existingEditor) { - existingEditor.remove(); - } - scriptList.insertBefore(configSection, scriptList.firstChild); - - } catch (error) { - showError('Error al cargar el esquema de configuración'); - } -} - -function createModal(title, content, onSave = null) { - const modal = document.createElement('div'); - modal.className = 'fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50 p-4'; - - modal.innerHTML = ` -
-
-

${title}

-
-
- ${content} -
-
- - ${onSave ? ` - - ` : ''} -
-
- `; - - document.body.appendChild(modal); - return modal; -} - -function addParameter(button) { - const parametersDiv = button.closest('.space-y-4').querySelector('#parameters'); - const newParam = document.createElement('div'); - newParam.className = 'parameter-item bg-gray-50 p-4 rounded-md relative'; - newParam.innerHTML = ` - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- `; - parametersDiv.appendChild(newParam); -} - -function removeParameter(button) { - button.closest('.parameter-item').remove(); -} - -function handleTypeChange(select) { - const paramItem = select.closest('.parameter-item'); - const optionsDiv = paramItem.querySelector('[name="param_options"]')?.closest('div'); - - if (select.value === 'select') { - if (!optionsDiv) { - const div = document.createElement('div'); - div.className = 'col-span-2'; - div.innerHTML = ` - - - `; - paramItem.querySelector('.grid').appendChild(div); - } - } else { - optionsDiv?.remove(); - } -} - -async function saveConfigSchema(groupId, modal) { - const form = modal.querySelector('div'); - const schema = { - group_name: form.querySelector('[name="group_name"]').value, - description: form.querySelector('[name="description"]').value, - config_schema: {} - }; - - // Recopilar parámetros - form.querySelectorAll('.parameter-item').forEach(item => { - const name = item.querySelector('[name="param_name"]').value; - const type = item.querySelector('[name="param_type"]').value; - const description = item.querySelector('[name="param_description"]').value; - const defaultValue = item.querySelector('[name="param_default"]').value; - - const param = { - type, - description, - default: type === 'boolean' ? defaultValue === 'true' : defaultValue - }; - - if (type === 'select') { - const options = item.querySelector('[name="param_options"]').value - .split(',') - .map(opt => opt.trim()) - .filter(Boolean); - param.options = options; - } - - schema.config_schema[name] = param; - }); - - try { - await apiRequest(`/script-groups/${groupId}/config-schema`, { - method: 'PUT', - body: JSON.stringify(schema) - }); - - closeModal(modal.querySelector('button')); - showSuccess('Configuration schema updated successfully'); - // Recargar la página para mostrar los cambios - loadGroupScripts(groupId); - } catch (error) { - showError('Failed to update configuration schema'); - } -} - -function showScriptForm(script) { - const modal = document.createElement('div'); - modal.className = 'modal active'; - - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); -} \ No newline at end of file diff --git a/claude/style.css b/claude/style.css deleted file mode 100644 index a343b9b..0000000 --- a/claude/style.css +++ /dev/null @@ -1,31 +0,0 @@ -/* frontend/static/css/style.css */ - -/* Solo mantenemos estilos específicos que no podemos lograr fácilmente con Tailwind */ -.output-area { - white-space: pre-wrap; - word-wrap: break-word; -} - -/* Estilos para modales que no se pueden lograr fácilmente con Tailwind */ -.modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} - -.modal-content { - background: white; - padding: 2rem; - border-radius: 0.5rem; - max-width: 600px; - width: 90%; - max-height: 90vh; - overflow-y: auto; -} \ No newline at end of file diff --git a/claude/workdir_config.js b/claude/workdir_config.js deleted file mode 100644 index 8d17144..0000000 --- a/claude/workdir_config.js +++ /dev/null @@ -1,161 +0,0 @@ -// frontend/static/js/workdir_config.js - -async function getWorkDirConfig() { - if (!currentProfile?.work_dir) { - showError('No se ha seleccionado un directorio de trabajo'); - return null; - } - - try { - return await apiRequest(`/workdir-config/${encodeURIComponent(currentProfile.work_dir)}`); - } catch (error) { - showError('Error al cargar la configuración del directorio de trabajo'); - return null; - } -} - -async function getGroupConfig(groupId) { - if (!currentProfile?.work_dir) { - showError('No se ha seleccionado un directorio de trabajo'); - return null; - } - - try { - return await apiRequest( - `/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}` - ); - } catch (error) { - showError('Error al cargar la configuración del grupo'); - return null; - } -} - -async function updateGroupConfig(groupId, settings) { - if (!currentProfile?.work_dir) { - showError('No se ha seleccionado un directorio de trabajo'); - return false; - } - - try { - await apiRequest( - `/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}`, - { - method: 'PUT', - body: JSON.stringify(settings) - } - ); - showSuccess('Group configuration updated successfully'); - return true; - } catch (error) { - showError('Failed to update group configuration'); - return false; - } -} - -function showConfigEditor(config, schema) { - const modal = document.createElement('div'); - modal.className = 'modal active'; - - const formContent = Object.entries(schema).map(([key, field]) => ` -
- - ${getInputByType(key, field, config[key])} -
- `).join(''); - - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); -} - -function getInputByType(key, field, value) { - switch (field.type) { - case 'select': - return ` - `; - case 'boolean': - return ` - `; - case 'number': - return ` - `; - default: - return ` - `; - } -} - -// static/js/workdir_config.js -async function showWorkDirConfig() { - if (!currentProfile?.work_dir) { - showError('No se ha seleccionado un directorio de trabajo'); - return; - } - - try { - const config = await getWorkDirConfig(); - - const content = ` -
-
-

Directory

-

${currentProfile.work_dir}

-
-
-

Version

-

${config.version}

-
-
-

Group Configurations

-
- ${Object.entries(config.group_settings || {}).map(([groupId, settings]) => ` -
-
${groupId}
-
${JSON.stringify(settings, null, 2)}
-
- `).join('')} -
-
-
- `; - - createModal('Work Directory Configuration', content); - } catch (error) { - showError('Error al cargar la configuración del directorio de trabajo'); - } -} - -function closeModal(button) { - const modal = button.closest('.modal'); - if (modal) { - modal.remove(); - } -} \ No newline at end of file diff --git a/claude/workdir_config.py b/claude/workdir_config.py deleted file mode 100644 index cb34d6a..0000000 --- a/claude/workdir_config.py +++ /dev/null @@ -1,72 +0,0 @@ -# backend/core/workdir_config.py -from pathlib import Path -import json -from typing import Dict, Any, Optional -from datetime import datetime - -class WorkDirConfigManager: - """Manages configuration files in work directories""" - - DEFAULT_CONFIG = { - "version": "1.0", - "created_at": "", - "updated_at": "", - "group_settings": {} - } - - def __init__(self, work_dir: str): - self.work_dir = Path(work_dir) - self.config_file = self.work_dir / "script_config.json" - - def get_config(self) -> Dict[str, Any]: - """Get configuration for work directory""" - if self.config_file.exists(): - try: - with open(self.config_file, 'r', encoding='utf-8') as f: - return json.load(f) - except Exception as e: - print(f"Error loading work dir config: {e}") - return self._create_default_config() - return self._create_default_config() - - def _create_default_config(self) -> Dict[str, Any]: - """Create default configuration""" - config = self.DEFAULT_CONFIG.copy() - now = datetime.now().isoformat() - config["created_at"] = now - config["updated_at"] = now - return config - - def save_config(self, config: Dict[str, Any]): - """Save configuration to file""" - # Ensure work directory exists - self.work_dir.mkdir(parents=True, exist_ok=True) - - # Update timestamp - config["updated_at"] = datetime.now().isoformat() - - # Save config - with open(self.config_file, 'w', encoding='utf-8') as f: - json.dump(config, f, indent=4) - - def get_group_config(self, group_id: str) -> Dict[str, Any]: - """Get configuration for specific script group""" - config = self.get_config() - return config["group_settings"].get(group_id, {}) - - def update_group_config(self, group_id: str, settings: Dict[str, Any]): - """Update configuration for specific script group""" - config = self.get_config() - - if "group_settings" not in config: - config["group_settings"] = {} - - config["group_settings"][group_id] = settings - self.save_config(config) - - def remove_group_config(self, group_id: str): - """Remove configuration for specific script group""" - config = self.get_config() - if group_id in config.get("group_settings", {}): - del config["group_settings"][group_id] - self.save_config(config) \ No newline at end of file diff --git a/claude/x1.py b/claude/x1.py deleted file mode 100644 index 257c8de..0000000 --- a/claude/x1.py +++ /dev/null @@ -1,108 +0,0 @@ -# backend/script_groups/example_group/x1.py -from backend.script_groups.base_script import BaseScript -import os -from pathlib import Path -import json -import csv -from datetime import datetime - -class FileCounter(BaseScript): - """ - File Analysis - Analyzes files in directory with configurable filters and reporting - """ - - def run(self, work_dir: str, profile: dict) -> dict: - try: - # Get configuration - config = self.get_config(work_dir, "example_group") - - # Process configuration values - exclude_dirs = [d.strip() for d in config.get("exclude_dirs", "").split(",") if d.strip()] - count_hidden = config.get("count_hidden", False) - min_size = config.get("min_size", 0) - save_report = config.get("save_report", True) - report_format = config.get("report_format", "json") - - # Initialize counters - extension_counts = {} - total_files = 0 - total_size = 0 - skipped_files = 0 - - # Walk through directory - for root, dirs, files in os.walk(work_dir): - # Skip excluded directories - dirs[:] = [d for d in dirs if d not in exclude_dirs] - - for file in files: - file_path = Path(root) / file - - # Skip hidden files if not counting them - if not count_hidden and file.startswith('.'): - skipped_files += 1 - continue - - # Check file size - try: - file_size = file_path.stat().st_size - if file_size < min_size: - skipped_files += 1 - continue - except: - continue - - # Count file - total_files += 1 - total_size += file_size - ext = file_path.suffix.lower() or 'no extension' - extension_counts[ext] = extension_counts.get(ext, 0) + 1 - - # Prepare results - results = { - "scan_time": datetime.now().isoformat(), - "total_files": total_files, - "total_size": total_size, - "skipped_files": skipped_files, - "extension_counts": extension_counts - } - - # Save report if configured - if save_report: - report_path = Path(work_dir) / f"file_analysis.{report_format}" - if report_format == "json": - with open(report_path, 'w') as f: - json.dump(results, f, indent=2) - elif report_format == "csv": - with open(report_path, 'w', newline='') as f: - writer = csv.writer(f) - writer.writerow(["Extension", "Count"]) - for ext, count in sorted(extension_counts.items()): - writer.writerow([ext, count]) - else: # txt - with open(report_path, 'w') as f: - f.write(f"File Analysis Report\n") - f.write(f"Generated: {results['scan_time']}\n\n") - f.write(f"Total Files: {total_files}\n") - f.write(f"Total Size: {total_size:,} bytes\n") - f.write(f"Skipped Files: {skipped_files}\n\n") - f.write("Extension Counts:\n") - for ext, count in sorted(extension_counts.items()): - f.write(f"{ext}: {count}\n") - - return { - "status": "success", - "data": results, - "output": f"Found {total_files:,} files ({total_size:,} bytes)\n" + - f"Skipped {skipped_files} files\n\n" + - "Extensions:\n" + "\n".join( - f"{ext}: {count:,} files" - for ext, count in sorted(extension_counts.items()) - ) - } - - except Exception as e: - return { - "status": "error", - "error": str(e) - } \ No newline at end of file diff --git a/claude/x2.py b/claude/x2.py deleted file mode 100644 index c3b805b..0000000 --- a/claude/x2.py +++ /dev/null @@ -1,104 +0,0 @@ -# backend/script_groups/example_group/x2.py -from backend.script_groups.base_script import BaseScript -import psutil -import json -from datetime import datetime -from pathlib import Path - -class SystemInfo(BaseScript): - """ - System Monitor - Collects and analyzes system performance metrics - """ - - def run(self, work_dir: str, profile: dict) -> dict: - try: - # Get configuration from the same config.json - config = self.get_config(work_dir, "example_group") - save_report = config.get("save_report", True) - report_format = config.get("report_format", "json") - - # Collect system information - cpu_freq = psutil.cpu_freq() - memory = psutil.virtual_memory() - disk = psutil.disk_usage(work_dir) - - info = { - "timestamp": datetime.now().isoformat(), - "cpu": { - "cores": psutil.cpu_count(), - "physical_cores": psutil.cpu_count(logical=False), - "frequency": { - "current": round(cpu_freq.current, 2) if cpu_freq else None, - "min": round(cpu_freq.min, 2) if cpu_freq else None, - "max": round(cpu_freq.max, 2) if cpu_freq else None - }, - "usage_percent": psutil.cpu_percent(interval=1) - }, - "memory": { - "total": memory.total, - "available": memory.available, - "used": memory.used, - "percent": memory.percent - }, - "disk": { - "total": disk.total, - "used": disk.used, - "free": disk.free, - "percent": disk.percent - }, - "network": { - "interfaces": list(psutil.net_if_addrs().keys()), - "connections": len(psutil.net_connections()) - } - } - - # Save report if configured - if save_report: - report_path = Path(work_dir) / f"system_info.{report_format}" - if report_format == "json": - with open(report_path, 'w') as f: - json.dump(info, f, indent=2) - elif report_format == "csv": - with open(report_path, 'w', newline='') as f: - writer = csv.writer(f) - writer.writerow(["Metric", "Value"]) - writer.writerow(["CPU Cores", info["cpu"]["cores"]]) - writer.writerow(["CPU Usage", f"{info['cpu']['usage_percent']}%"]) - writer.writerow(["Memory Total", f"{info['memory']['total']:,} bytes"]) - writer.writerow(["Memory Used", f"{info['memory']['percent']}%"]) - writer.writerow(["Disk Total", f"{info['disk']['total']:,} bytes"]) - writer.writerow(["Disk Used", f"{info['disk']['percent']}%"]) - else: # txt - with open(report_path, 'w') as f: - f.write(f"System Information Report\n") - f.write(f"Generated: {info['timestamp']}\n\n") - f.write(f"CPU:\n") - f.write(f" Cores: {info['cpu']['cores']}\n") - f.write(f" Usage: {info['cpu']['usage_percent']}%\n\n") - f.write(f"Memory:\n") - f.write(f" Total: {info['memory']['total']:,} bytes\n") - f.write(f" Used: {info['memory']['percent']}%\n\n") - f.write(f"Disk:\n") - f.write(f" Total: {info['disk']['total']:,} bytes\n") - f.write(f" Used: {info['disk']['percent']}%\n") - - # Format output - output = f"""System Information: -CPU: {info['cpu']['cores']} cores ({info['cpu']['usage_percent']}% usage) -Memory: {info['memory']['percent']}% used ({info['memory']['available']:,} bytes available) -Disk: {info['disk']['percent']}% used ({info['disk']['free']:,} bytes free) -Network Interfaces: {', '.join(info['network']['interfaces'])} -Active Connections: {info['network']['connections']}""" - - return { - "status": "success", - "data": info, - "output": output - } - - except Exception as e: - return { - "status": "error", - "error": str(e) - } \ No newline at end of file diff --git a/data/profile_schema.json b/data/profile_schema.json new file mode 100644 index 0000000..522d87f --- /dev/null +++ b/data/profile_schema.json @@ -0,0 +1,39 @@ +{ + "description": "Configuration schema for application profiles", + "config_schema": { + "id": { + "type": "string", + "description": "Unique identifier for the profile", + "required": true + }, + "name": { + "type": "string", + "description": "Display name for the profile", + "required": true + }, + "llm_settings": { + "type": "object", + "description": "Language model settings", + "properties": { + "model": { + "type": "select", + "description": "Language model to use", + "options": ["gpt-4", "gpt-3.5-turbo"], + "default": "gpt-4" + }, + "temperature": { + "type": "number", + "description": "Temperature for text generation", + "default": 0.7, + "min": 0, + "max": 2 + }, + "api_key": { + "type": "string", + "description": "API key for the language model", + "default": "" + } + } + } + } +} \ No newline at end of file diff --git a/data/profiles.json b/data/profiles.json index 3b68f75..0e256b3 100644 --- a/data/profiles.json +++ b/data/profiles.json @@ -2,25 +2,23 @@ "default": { "id": "default", "name": "Default Profile", - "work_dir": "", "llm_settings": { "model": "gpt-4", "temperature": 0.7, "api_key": "" }, - "created_at": "2025-02-07T12:47:49.766608", - "updated_at": "2025-02-07T12:47:49.766608" + "created_at": "2025-02-08T12:00:00.000Z", + "updated_at": "2025-02-08T12:00:00.000Z" }, "1": { "id": "1", "name": "Base", - "work_dir": "C:/Estudio", "llm_settings": { "api_key": "333333333333", "model": "gpt-4", "temperature": 0.7 }, - "created_at": "2025-02-07T13:00:43.541932", - "updated_at": "2025-02-07T23:34:43.039269" + "created_at": "2025-02-08T13:00:43.541932", + "updated_at": "2025-02-08T23:34:43.039269" } } \ No newline at end of file diff --git a/frontend/static/css/style.css b/frontend/static/css/style.css index a343b9b..78339b4 100644 --- a/frontend/static/css/style.css +++ b/frontend/static/css/style.css @@ -1,12 +1,12 @@ /* frontend/static/css/style.css */ -/* Solo mantenemos estilos específicos que no podemos lograr fácilmente con Tailwind */ +/* Estilos para el área de salida que requiere white-space específico */ .output-area { white-space: pre-wrap; word-wrap: break-word; } -/* Estilos para modales que no se pueden lograr fácilmente con Tailwind */ +/* Estilos para modales - solo lo que no se puede hacer con Tailwind */ .modal { position: fixed; top: 0; @@ -21,11 +21,6 @@ } .modal-content { - background: white; - padding: 2rem; - border-radius: 0.5rem; - max-width: 600px; - width: 90%; max-height: 90vh; overflow-y: auto; } \ No newline at end of file diff --git a/frontend/static/js/main.js b/frontend/static/js/main.js index 951ab99..3737acf 100644 --- a/frontend/static/js/main.js +++ b/frontend/static/js/main.js @@ -1,9 +1,10 @@ // frontend/static/js/main.js -// Global state +// Estado global let currentProfile = null; +let currentGroup = null; -// Definir clases comunes para inputs +// Estilos comunes const STYLES = { editableInput: "mt-1 block w-full rounded-md border-2 border-gray-300 bg-green-50 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500", readonlyInput: "mt-1 block w-full rounded-md border-2 border-gray-200 bg-gray-100 px-3 py-2 shadow-sm", @@ -11,9 +12,10 @@ const STYLES = { buttonSecondary: "px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300" }; +// Inicialización de la aplicación async function initializeApp() { try { - console.log('Inicializando aplicación...'); + console.log('Initializing application...'); // Cargar perfiles const profiles = await apiRequest('/profiles'); @@ -33,66 +35,16 @@ async function initializeApp() { await selectProfile(selectedProfile.id); } - // Cargar grupos de scripts y restaurar la última selección - await restoreScriptGroup(); - - // Actualizar la interfaz - updateWorkDirDisplay(); + // Restaurar último estado + await restoreLastState(); } catch (error) { - console.error('Error al inicializar la aplicación:', error); - showError('Error al inicializar la aplicación'); + console.error('Error initializing application:', error); + showError('Error initializing application'); } } -async function restoreScriptGroup() { - try { - // Primero cargar los grupos disponibles - await loadScriptGroups(); - - // Luego intentar restaurar el último grupo seleccionado - const lastGroupId = localStorage.getItem('lastGroupId'); - if (lastGroupId) { - console.log('Restoring last group:', lastGroupId); - const groupSelect = document.getElementById('groupSelect'); - if (groupSelect) { - groupSelect.value = lastGroupId; - if (groupSelect.value) { // Verifica que el valor se haya establecido correctamente - await loadGroupScripts(lastGroupId); - } else { - console.log('Selected group no longer exists:', lastGroupId); - localStorage.removeItem('lastGroupId'); - } - } - } - } catch (error) { - console.error('Error restoring script group:', error); - } -} - -// Función para restaurar el último estado -async function restoreLastState() { - const lastProfileId = localStorage.getItem('lastProfileId'); - const lastGroupId = localStorage.getItem('lastGroupId'); - - console.log('Restoring last state:', { lastProfileId, lastGroupId }); - - if (lastProfileId) { - const profileSelect = document.getElementById('profileSelect'); - profileSelect.value = lastProfileId; - await selectProfile(lastProfileId); - } - - if (lastGroupId) { - const groupSelect = document.getElementById('groupSelect'); - if (groupSelect) { - groupSelect.value = lastGroupId; - await loadGroupScripts(lastGroupId); - } - } -} - -// API functions +// Funciones de API async function apiRequest(endpoint, options = {}) { try { const response = await fetch(`/api${endpoint}`, { @@ -105,127 +57,110 @@ async function apiRequest(endpoint, options = {}) { if (!response.ok) { const error = await response.json(); - throw new Error(error.error || 'Error en la solicitud API'); + throw new Error(error.error || 'API request error'); } return await response.json(); } catch (error) { - console.error('Error API:', error); + console.error('API Error:', error); showError(error.message); throw error; } } -async function loadProfiles() { - try { - const profiles = await apiRequest('/profiles'); - updateProfileSelector(profiles); - - // Obtener último perfil usado - const lastProfileId = localStorage.getItem('lastProfileId'); - - // Seleccionar perfil guardado o el default - const defaultProfile = profiles.find(p => p.id === (lastProfileId || 'default')) || profiles[0]; - if (defaultProfile) { - await selectProfile(defaultProfile.id); - } - } catch (error) { - showError('Error al cargar los perfiles'); - } -} - +// Funciones de gestión de perfiles async function selectProfile(profileId) { try { - console.log('Seleccionando perfil:', profileId); + console.log('Selecting profile:', profileId); + + // Cargar perfil currentProfile = await apiRequest(`/profiles/${profileId}`); // Guardar en localStorage localStorage.setItem('lastProfileId', profileId); - console.log('Profile ID saved to storage:', profileId); + console.log('Profile ID saved:', profileId); - // Actualizar explícitamente el valor del combo - const select = document.getElementById('profileSelect'); - if (select) { - select.value = profileId; - console.log('Updated profileSelect value to:', profileId); - } + // Actualizar UI + updateProfileSelector([currentProfile]); + updateProfileDisplay(); - updateWorkDirDisplay(); - - // Recargar scripts con el último grupo seleccionado - await restoreScriptGroup(); + return currentProfile; } catch (error) { - console.error('Error al seleccionar perfil:', error); - showError('Error al cargar el perfil'); + console.error('Error selecting profile:', error); + showError('Error loading profile'); + throw error; } } -// Initialize when page loads -document.addEventListener('DOMContentLoaded', initializeApp); - function updateProfileSelector(profiles) { const select = document.getElementById('profileSelect'); + if (!select) return; + const lastProfileId = localStorage.getItem('lastProfileId') || 'default'; - console.log('Updating profile selector. Last profile ID:', lastProfileId); - - // Construir las opciones select.innerHTML = profiles.map(profile => ` `).join(''); - - // Asegurar que el valor seleccionado sea correcto - select.value = lastProfileId; - console.log('Set profileSelect value to:', lastProfileId); } -async function changeProfile() { - const select = document.getElementById('profileSelect'); - if (select.value) { - await selectProfile(select.value); - await loadScriptGroups(); // Reload scripts when profile changes - } +function updateProfileDisplay() { + const container = document.getElementById('profileConfig'); + if (!container || !currentProfile) return; + + container.innerHTML = ` +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ `; } -// Work directory functions -function updateWorkDirDisplay() { - const input = document.getElementById('workDirPath'); - if (input && currentProfile) { - input.value = currentProfile.work_dir || ''; - } -} - -async function selectWorkDir() { +// Funciones de estado +async function restoreLastState() { try { - console.log('Requesting directory selection...'); // Debug - const response = await apiRequest('/select-directory'); - console.log('Directory selection response:', response); // Debug - - if (response.path) { - console.log('Updating profile with new work_dir:', response.path); // Debug - const updateResponse = await apiRequest(`/profiles/${currentProfile.id}`, { - method: 'PUT', - body: JSON.stringify({ - ...currentProfile, - work_dir: response.path - }) - }); - console.log('Profile update response:', updateResponse); // Debug - - await selectProfile(currentProfile.id); - showSuccess('Directorio de trabajo actualizado correctamente'); + // Restaurar último grupo seleccionado + const lastGroupId = localStorage.getItem('lastGroupId'); + if (lastGroupId) { + const groupSelect = document.getElementById('groupSelect'); + if (groupSelect) { + groupSelect.value = lastGroupId; + await handleGroupChange({ target: { value: lastGroupId } }); + } } } catch (error) { - console.error('Error al seleccionar directorio:', error); // Debug - showError('Error al actualizar el directorio de trabajo'); + console.error('Error restoring state:', error); } } -// Output functions +// Funciones de utilidad UI function showError(message) { const output = document.getElementById('outputArea'); + if (!output) return; + const timestamp = new Date().toLocaleTimeString(); output.innerHTML += `\n[${timestamp}] ERROR: ${message}`; output.scrollTop = output.scrollHeight; @@ -233,6 +168,8 @@ function showError(message) { function showSuccess(message) { const output = document.getElementById('outputArea'); + if (!output) return; + const timestamp = new Date().toLocaleTimeString(); output.innerHTML += `\n[${timestamp}] SUCCESS: ${message}`; output.scrollTop = output.scrollHeight; @@ -240,10 +177,32 @@ function showSuccess(message) { function clearOutput() { const output = document.getElementById('outputArea'); - output.innerHTML = ''; + if (output) { + output.innerHTML = ''; + } } -// Modal helper functions +// Event Handlers +async function handleProfileChange(event) { + const profileId = event.target.value; + if (profileId) { + await selectProfile(profileId); + } +} + +async function handleGroupChange(event) { + const groupId = event.target.value; + if (groupId) { + localStorage.setItem('lastGroupId', groupId); + await selectGroup(groupId); + } else { + localStorage.removeItem('lastGroupId'); + currentGroup = null; + updateGroupDisplay(); + } +} + +// Modal Helpers function closeModal(button) { const modal = button.closest('.modal'); if (modal) { @@ -251,14 +210,17 @@ function closeModal(button) { } } -// Global error handler +// Error Handler Global window.addEventListener('unhandledrejection', function(event) { console.error('Unhandled promise rejection:', event.reason); showError('An unexpected error occurred'); }); -// Export functions for use in other modules +// Exportar funciones globales window.showError = showError; window.showSuccess = showSuccess; window.closeModal = closeModal; -window.currentProfile = currentProfile; \ No newline at end of file +window.STYLES = STYLES; + +// Inicializar cuando la página carga +document.addEventListener('DOMContentLoaded', initializeApp); \ No newline at end of file diff --git a/frontend/static/js/profile.js b/frontend/static/js/profile.js index 0a5c615..93ce984 100644 --- a/frontend/static/js/profile.js +++ b/frontend/static/js/profile.js @@ -5,66 +5,125 @@ let editingProfile = null; async function loadProfiles() { try { const response = await apiRequest('/profiles'); - const profiles = Object.values(response); + if (!response || !Object.keys(response).length) { + throw new Error('No profiles available'); + } - // Actualizar el selector manteniendo el valor seleccionado + const profiles = Object.values(response); const select = document.getElementById('profileSelect'); + + // Actualizar el selector select.innerHTML = profiles.map(profile => ` - `).join(''); - // Establecer el valor seleccionado después de actualizar las opciones - if (response[selectedProfileId]) { + // Intentar seleccionar el perfil guardado o el predeterminado + const savedProfile = profiles.find(p => p.id === selectedProfileId); + if (savedProfile) { + await selectProfile(savedProfile.id); + } else { + // Si no se encuentra el perfil guardado, usar el primero disponible + selectedProfileId = profiles[0].id; select.value = selectedProfileId; await selectProfile(selectedProfileId); - } else { - selectedProfileId = 'default'; - select.value = 'default'; - await selectProfile('default'); } - // Asegurarse de que el evento change no sobrescriba la selección - select.addEventListener('change', onProfileChange, { once: true }); + localStorage.setItem('selectedProfileId', selectedProfileId); } catch (error) { - showError('Error al cargar los perfiles'); + console.error('Error loading profiles:', error); + showError('Error loading profiles. Using default profile.'); + await loadDefaultProfile(); + } +} + +async function loadDefaultProfile() { + try { + currentProfile = await apiRequest('/profiles/default'); + selectedProfileId = 'default'; + localStorage.setItem('selectedProfileId', 'default'); + + const select = document.getElementById('profileSelect'); + select.innerHTML = ``; + select.value = 'default'; + + // Actualizar la visualización del perfil + updateProfileDisplay(); + + } catch (error) { + console.error('Error loading default profile:', error); + showError('Failed to load default profile'); } } async function selectProfile(profileId) { try { - currentProfile = await apiRequest(`/profiles/${profileId}`); - updateWorkDirDisplay(); + const response = await apiRequest(`/profiles/${profileId}`); + if (!response || response.error) { + throw new Error(response?.error || 'Profile not found'); + } + + currentProfile = response; + selectedProfileId = profileId; + localStorage.setItem('selectedProfileId', profileId); + + // Actualizar la visualización del perfil + updateProfileDisplay(); + } catch (error) { - showError('Failed to load profile'); + console.error('Failed to load profile:', error); + showError(`Failed to load profile: ${error.message}`); + // Intentar cargar el perfil por defecto si falla + if (profileId !== 'default') { + await loadDefaultProfile(); + } } } +function updateProfileDisplay() { + const profileConfig = document.getElementById('profileConfig'); + if (!profileConfig || !currentProfile) return; + + profileConfig.innerHTML = ` +
+
+
+ +
${currentProfile.id}
+
+
+ +
${currentProfile.name}
+
+
+
+
+ +
${currentProfile.llm_settings?.model || 'Not set'}
+
+
+ +
${currentProfile.llm_settings?.temperature || 'Not set'}
+
+
+
+ +
+ ${currentProfile.llm_settings?.api_key ? '********' : 'Not set'} +
+
+
+ `; +} + async function changeProfile() { const select = document.getElementById('profileSelect'); await selectProfile(select.value); } -async function selectWorkDir() { - try { - const response = await apiRequest('/select-directory'); - if (response.path) { - await apiRequest(`/profiles/${currentProfile.id}`, { - method: 'PUT', - body: JSON.stringify({ - ...currentProfile, - work_dir: response.path - }) - }); - await selectProfile(currentProfile.id); - showSuccess('Work directory updated successfully'); - } - } catch (error) { - showError('Failed to update work directory'); - } -} +// Eliminar la función updateWorkDirDisplay y selectWorkDir // Profile editor modal @@ -95,12 +154,6 @@ function showProfileEditor(profile = null) { class="${editableInputClass}" value="${profile?.name || ''}" required> -
- - -
-
-
-
-
- - -
- - - `; - - document.body.appendChild(modal); -} - -function generateFormField(key, field, currentValue) { - switch (field.type) { - case 'string': - return ` - - `; - case 'number': - return ` - - `; - case 'boolean': - return ` - - `; - case 'select': - return ` - - `; - default: - return ``; - } -} - -async function loadGroupScripts(groupId) { - const scriptList = document.getElementById('scriptList'); - - if (!groupId) { - scriptList.style.display = 'none'; - localStorage.removeItem('lastGroupId'); - return; - } - - if (!currentProfile?.work_dir) { - scriptList.innerHTML = ` -
-
-
-

- Por favor, seleccione primero un directorio de trabajo -

-
-
-
- `; - scriptList.style.display = 'block'; - return; - } - - try { - console.log('Loading data for group:', groupId); - - // Cargar y loguear scripts - let groupScripts, configSchema; - try { - groupScripts = await apiRequest(`/script-groups/${groupId}/scripts`); - console.log('Scripts loaded:', groupScripts); - } catch (e) { - console.error('Error loading scripts:', e); - throw e; - } - - try { - configSchema = await apiRequest(`/script-groups/${groupId}/config-schema`); - console.log('Config schema loaded:', configSchema); - } catch (e) { - console.error('Error loading config schema:', e); - throw e; - } - - // Intentar cargar configuración actual - let currentConfig = {}; - try { - console.log('Loading current config for work_dir:', currentProfile.work_dir); - currentConfig = await apiRequest( - `/workdir-config/${encodeURIComponent(currentProfile.work_dir)}/group/${groupId}` - ); - console.log('Current config loaded:', currentConfig); - } catch (e) { - console.warn('No existing configuration found, using defaults'); - } - - // Verificar que tenemos los datos necesarios - if (!groupScripts || !configSchema) { - throw new Error('Failed to load required data'); - } - - console.log('Rendering UI with:', { - groupScripts, - configSchema, - currentConfig - }); - - scriptList.innerHTML = ` - -
-
+ const schema = await apiRequest(`/script-groups/${currentGroup.id}/schema`); + const content = ` +
+ ${Object.entries(schema.config_schema).map(([key, field]) => `
-

- ${configSchema.group_name || 'Configuración'} -

-

${configSchema.description || ''}

-
- -
-
- - ${Object.entries(configSchema.config_schema || {}).map(([key, field]) => ` -
- - ${generateFormField(key, field, currentConfig[key])} -
- `).join('')} -
- -
- -
-
- - -
- ${groupScripts.map(script => ` -
-
-
-

${script.name || script.id}

-

${script.description || 'Sin descripción disponible'}

-
- -
+ + ${generateFormField(key, field, currentGroup[key])}
`).join('')} -
`; - - scriptList.style.display = 'block'; - - // Agregar evento para guardar configuración - const form = document.getElementById('groupConfigForm'); - form.addEventListener('submit', async (e) => { - e.preventDefault(); - await saveGroupConfig(groupId, form); - }); - - } catch (error) { - console.error('Error in loadGroupScripts:', error); - showError('Failed to load scripts and configuration'); - } -} - -async function editConfigSchema(groupId) { - try { - const schema = await apiRequest(`/script-groups/${groupId}/config-schema`); - const configSection = document.createElement('div'); - configSection.id = 'schemaEditor'; - configSection.className = 'mb-6 bg-white shadow sm:rounded-lg'; - - configSection.innerHTML = ` -
-

Editar Configuración del Esquema

- -
-
-
-
-
- - -
-
- -
-
-
- - -
-
-
-

Parámetros

-
- ${Object.entries(schema.config_schema).map(([key, param]) => ` -
- -
-
- - -
-
- - -
-
- - -
-
- - -
- ${param.type === 'select' ? ` -
- - -
- ` : ''} -
-
- `).join('')} -
-
- - -
-
-
+ `; - // Insertamos el editor justo después del botón "Edit Schema" - const scriptList = document.getElementById('scriptList'); - const existingEditor = document.getElementById('schemaEditor'); - if (existingEditor) { - existingEditor.remove(); - } - scriptList.insertBefore(configSection, scriptList.firstChild); - + const modal = createModal('Edit Group Configuration', content, true); + modal.querySelector('[onclick="saveModal(this)"]').onclick = () => saveGroupConfig(modal); } catch (error) { - showError('Error al cargar el esquema de configuración'); + showError('Error loading group configuration'); } } -function createModal(title, content, onSave = null) { - const modal = document.createElement('div'); - modal.className = 'fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50 p-4'; - - modal.innerHTML = ` -
-
-

${title}

-
-
- ${content} -
-
- - ${onSave ? ` - - ` : ''} -
-
- `; +async function saveGroupConfig(modal) { + if (!currentGroup) return; - document.body.appendChild(modal); - return modal; -} + const form = modal.querySelector('#groupConfigForm'); + const formData = new FormData(form); + const config = {}; -function addParameter(button) { - const parametersDiv = button.closest('.space-y-4').querySelector('#parameters'); - const newParam = document.createElement('div'); - newParam.className = 'parameter-item bg-gray-50 p-4 rounded-md relative'; - newParam.innerHTML = ` - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- `; - parametersDiv.appendChild(newParam); -} - -function removeParameter(button) { - button.closest('.parameter-item').remove(); -} - -function handleTypeChange(select) { - const paramItem = select.closest('.parameter-item'); - const optionsDiv = paramItem.querySelector('[name="param_options"]')?.closest('div'); - - if (select.value === 'select') { - if (!optionsDiv) { - const div = document.createElement('div'); - div.className = 'col-span-2'; - div.innerHTML = ` - - - `; - paramItem.querySelector('.grid').appendChild(div); - } - } else { - optionsDiv?.remove(); - } -} - -async function saveConfigSchema(groupId, modal) { - const form = modal.querySelector('div'); - const schema = { - group_name: form.querySelector('[name="group_name"]').value, - description: form.querySelector('[name="description"]').value, - config_schema: {} - }; - - // Recopilar parámetros - form.querySelectorAll('.parameter-item').forEach(item => { - const name = item.querySelector('[name="param_name"]').value; - const type = item.querySelector('[name="param_type"]').value; - const description = item.querySelector('[name="param_description"]').value; - const defaultValue = item.querySelector('[name="param_default"]').value; - - const param = { - type, - description, - default: type === 'boolean' ? defaultValue === 'true' : defaultValue - }; - - if (type === 'select') { - const options = item.querySelector('[name="param_options"]').value - .split(',') - .map(opt => opt.trim()) - .filter(Boolean); - param.options = options; - } - - schema.config_schema[name] = param; + formData.forEach((value, key) => { + if (value === 'true') value = true; + else if (value === 'false') value = false; + else if (!isNaN(value) && value !== '') value = Number(value); + config[key] = value; }); try { - await apiRequest(`/script-groups/${groupId}/config-schema`, { + await apiRequest(`/script-groups/${currentGroup.id}/config`, { + method: 'PUT', + body: JSON.stringify(config) + }); + + closeModal(modal); + showSuccess('Group configuration updated'); + await selectGroup(currentGroup.id); + } catch (error) { + showError('Error saving group configuration'); + } +} + +async function editGroupSchema() { + if (!currentGroup) return; + + try { + const schema = await apiRequest(`/script-groups/${currentGroup.id}/schema`); + + const content = ` +
+
+
+ + +
+
+
+ +
+
+
+
+ + +
+
+ ${Object.entries(schema.config_schema || {}).map(([key, field]) => + generateSchemaField(key, field)).join('')} +
+
+ `; + + const modal = createModal('Edit Group Schema', content, true); + modal.querySelector('[onclick="saveModal(this)"]').onclick = () => saveGroupSchema(modal); + } catch (error) { + showError('Error loading group schema'); + } +} + +async function saveGroupSchema(modal) { + const schema = { + group_name: modal.querySelector('[name="group_name"]').value, + description: modal.querySelector('[name="description"]').value, + config_schema: {} + }; + + // Recopilar definiciones de campos + modal.querySelectorAll('.schema-field').forEach(field => { + const key = field.querySelector('[name="field_name"]').value; + const type = field.querySelector('[name="field_type"]').value; + + if (!key) return; // Ignorar campos sin nombre + + const fieldSchema = { + type, + description: field.querySelector('[name="field_description"]').value, + required: field.querySelector('[name="field_required"]').value === 'true' + }; + + // Procesar valor por defecto según el tipo + const defaultValue = field.querySelector('[name="field_default"]').value; + if (defaultValue) { + if (type === 'number') { + fieldSchema.default = Number(defaultValue); + } else if (type === 'boolean') { + fieldSchema.default = defaultValue === 'true'; + } else { + fieldSchema.default = defaultValue; + } + } + + // Procesar opciones para campos tipo select + if (type === 'select') { + const optionsStr = field.querySelector('[name="field_options"]').value; + fieldSchema.options = optionsStr.split(',').map(opt => opt.trim()).filter(Boolean); + } + + schema.config_schema[key] = fieldSchema; + }); + + try { + await apiRequest(`/script-groups/${currentGroup.id}/schema`, { method: 'PUT', body: JSON.stringify(schema) }); - closeModal(modal.querySelector('button')); - showSuccess('Configuration schema updated successfully'); - // Recargar la página para mostrar los cambios - loadGroupScripts(groupId); + closeModal(modal); + showSuccess('Group schema updated'); + + // Recargar configuración del grupo + await editGroupConfig(); } catch (error) { - showError('Failed to update configuration schema'); + showError('Error saving group schema'); } } -function showScriptForm(script) { - const modal = document.createElement('div'); - modal.className = 'modal active'; - - modal.innerHTML = ` -