From abe66bc1d933ed7a1b163a07e5e991ec7ecc4fe4 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 8 Feb 2025 15:23:14 +0100 Subject: [PATCH] Mejorado con la edicion de los parametros dentro de la pagina principal --- backend/app.py | 49 +- .../directory_handler.cpython-310.pyc | Bin 1016 -> 860 bytes .../script_manager.cpython-310.pyc | Bin 4252 -> 5118 bytes backend/core/directory_handler.py | 14 +- backend/core/script_manager.py | 45 +- .../__pycache__/x1.cpython-310.pyc | Bin 1657 -> 3080 bytes .../__pycache__/x2.cpython-310.pyc | Bin 1657 -> 3150 bytes .../script_groups/example_group/config.json | 32 + backend/script_groups/example_group/x1.py | 85 ++- backend/script_groups/example_group/x2.py | 87 ++- claude_file_organizer.py | 171 +++++ files.txt | Bin 2666 -> 3224 bytes frontend/static/css/style.css | 171 +---- frontend/static/js/main.js | 144 +++- frontend/static/js/modal.js | 38 + frontend/static/js/profile.js | 104 ++- frontend/static/js/scripts.js | 653 +++++++++++++++++- frontend/static/js/workdir_config.js | 47 +- frontend/templates/index.html | 143 ++-- 19 files changed, 1429 insertions(+), 354 deletions(-) create mode 100644 backend/script_groups/example_group/config.json create mode 100644 claude_file_organizer.py create mode 100644 frontend/static/js/modal.js diff --git a/backend/app.py b/backend/app.py index 20bc044..1bf0035 100644 --- a/backend/app.py +++ b/backend/app.py @@ -84,17 +84,6 @@ def get_script_groups(): 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: - scripts = script_manager.get_group_scripts(group_id) - return jsonify(scripts) - except ValueError as e: - return jsonify({"error": str(e)}), 404 - except Exception as e: - return jsonify({"error": str(e)}), 500 - # Directory handling endpoints @app.route('/api/select-directory', methods=['GET']) def handle_select_directory(): @@ -162,5 +151,43 @@ def update_group_config(work_dir, group_id): 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/backend/core/__pycache__/directory_handler.cpython-310.pyc b/backend/core/__pycache__/directory_handler.cpython-310.pyc index 8f1efa00d6707dfa1ca59423a41e13220c91abed..b82437b665c467b5249da91a5c5e1d3981e96c17 100644 GIT binary patch delta 360 zcmY*UJxjw-6utMp*Cc)Y0BaS+#fl0YM8~-5;O1hX+O72!O^ZfepM-`)1wn9dkU$4r zno0Z_{t1WR;Lp&ZVuB!g;hw`ioO9rQm~TeeEtLeM^SJ+X^g{RL(Irc-OxjL&@Q{Lr zF~$%=0Aq>)GiDMc#f0E*`U{%(a*}DIXO1n&W0bi&3Nb)clSY6Jo-%1l%fTizvur-= z*aHq)40jO1tYu1?x)9KH>fUIA0S6%SAzjelz>ngg_gmsYUHEo%gLc8U*0g(e;tq5p zddKx>g_di(^4{O1;FZOTR+zJ&1IYXxct_k9lu?|Nk49pWg3kpy%cUxS)$~-79t-+?;aP zjYe-=_HSLWFu)sP#yS^4N>w-%F*3cGW-HxX~bk` zUP&)Qg)x&==_}015w(n_1a+V@@~;iy6rl|qtT_3AkTbXLnS+I$$=LbSQdTFu!VY+_ z4RR)DpV_0u$YC-`FnO>=NMLz3T@#^;_P$O+sZ8|AYI73yqn~=*(`2wL2%PD@+&HjnO7l z@x#iG(>Juj&E?sW?BNc*Kiu&XW%s4oP0GrVQL!xqt-atSH(J}WHWiiL#JR-iJD7zc zqP^y8_hCNwk&{a#6zJUtyp92SC@R6-JU&H1{u$i~ZW_feT5U9(#4P+@>P3q!{Q-Ml Bj=2B; diff --git a/backend/core/__pycache__/script_manager.cpython-310.pyc b/backend/core/__pycache__/script_manager.cpython-310.pyc index 97eb1135a2a45bceb37af32296d2b2d171e53e5e..bbb385910b2a4af4cb8162ba0002d9b983ed2675 100644 GIT binary patch delta 2715 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 z 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 = [] @@ -23,18 +47,20 @@ class ScriptManager: 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']) - """Manages script discovery and execution""" def __init__(self, script_groups_dir: Path): self.script_groups_dir = script_groups_dir @@ -92,10 +118,19 @@ class ScriptManager: 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": script_class.__doc__.split('\n')[0].strip() if script_class.__doc__ else script_file.stem, - "description": inspect.getdoc(script_class), + "name": name.strip(), + "description": description.strip(), "file": str(script_file.relative_to(self.script_groups_dir)) } diff --git a/backend/script_groups/example_group/__pycache__/x1.cpython-310.pyc b/backend/script_groups/example_group/__pycache__/x1.cpython-310.pyc index ce249e22fdbda321444a350742c9358f395e58e9..f90b7e79e523b6b35275661765a1ca996578c0ed 100644 GIT binary patch literal 3080 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 K=qxwTG5jCfV^CiJ literal 1657 zcmZWp&2J+$6t_JdXEIIN^n;IXccpLuF_*$}tX8O^dLBF47R6f zq68szg}AKv11O@oAn`Bx%Be@LNNstZ39ByHvi;t({XTwvetEm?BWUfPem@}|LVsH3 zVWYt8!cfm(;)vrE4RH@+OOurJNVPgW2UaJgL)xPleTF#Y?sLRlfiIn&%gG@M*bQhv z0XF_ekrIcoObWdZtj8lQbTSmN-GA5^FuO3+J1_~-dB^1IM#LDx#;Fw#k$RpPxoO+~C#H_CXIC#p!JvFaX2Dv7(Q z($P)+bH#Y0T%LtIk)~0|e2}EVFe!8?v)BPA|7}emEUtSn)GkJ7@}oxAc#bqWg$Ou# zfw^-g_<{od(iH!8}6klTQZ=sp1SEepsx+Fh$W=wZZSEtUD@fL5ta4J=ofzG_A zNcBhX&bEI8gD+1VzOspUXA}K`INYqbwTfE@ZUgpStGL&Jd*dbd=C93JgKvVR27h$r z9HB$7q1VR0VKnnFnzZ?rUY~m4;o~cEgcw-ku=BllxL8Mcgn}pgHvpqeA1}$Ykmpev z5aY(V6w0`zicUn}7+2>yN=-95kCHSxhSzBnLdGJ~fg@XR-_U^+LM~fX)Hrcb8a5Qe zT#gNe6gV?Yn~6$C!@{)11uUruPh?&ey^e~`MOY_BXcObpN#_B#eX8=z;L#1K-DF9| z!R}=9|Jk?W?;U)-)0G`KDQ%ieylxdp_g%SU+10nZbwvJ`m2Yr%)tTn0pLT78dU3N{QYRH+ky4d4jl zyv%fO$&w4`ddugD)Mb>0HBuFjvTYSuhm3E53oD1^ij~@*V4N%;nP#H$`qLTzL}*Z+ z4U!Y%0Bz_&q`x&ZFGOZ&1s>%kz~21iA{K@HrN&WO22DeiNC(ojs%=Ry)S_=#t*p0H zhZa_!5f9{+J%Ys_NWKTu-}`*0e<1TQ{PbM)>$0QzU*vI=E|?>6+&_-uGm-JWs>)LR z;;R2j0`xCF*e=G#k!5CMSNkv#%&3p)o#%Sk!}Qi?J_i2QciCU9^%k~$4L`mo_SAhf z2)*+i@0Kuv3HEN686ga>-FZKd`^j>hE4EN=gPHxHVZ1QRqM-;woe=OyH0Xj)Jw6h0;S5KXNNsz|30<6t-VMVH+wU t*vZ!9>ZPmO53E?!r8Q-|XSKs0+MlazqPGAg4=~vv{{Zoc!L$GX diff --git a/backend/script_groups/example_group/config.json b/backend/script_groups/example_group/config.json new file mode 100644 index 0000000..36be0b3 --- /dev/null +++ b/backend/script_groups/example_group/config.json @@ -0,0 +1,32 @@ +{ + "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/backend/script_groups/example_group/x1.py b/backend/script_groups/example_group/x1.py index f5ba050..257c8de 100644 --- a/backend/script_groups/example_group/x1.py +++ b/backend/script_groups/example_group/x1.py @@ -2,22 +2,33 @@ 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): """ - Count Files in Directory - Lists and counts files in the working directory by extension + File Analysis + Analyzes files in directory with configurable filters and reporting """ def run(self, work_dir: str, profile: dict) -> dict: try: - # Get configuration if any + # Get configuration config = self.get_config(work_dir, "example_group") - exclude_dirs = config.get("exclude_dirs", []) + + # 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): @@ -25,18 +36,67 @@ class FileCounter(BaseScript): 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 - ext = Path(file).suffix.lower() or 'no extension' + 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": { - "total_files": total_files, - "extension_counts": extension_counts - }, - "output": f"Found {total_files} files\n" + "\n".join( - f"{ext}: {count} files" + "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()) ) } @@ -45,5 +105,4 @@ class FileCounter(BaseScript): return { "status": "error", "error": str(e) - } - + } \ No newline at end of file diff --git a/backend/script_groups/example_group/x2.py b/backend/script_groups/example_group/x2.py index 917c107..c3b805b 100644 --- a/backend/script_groups/example_group/x2.py +++ b/backend/script_groups/example_group/x2.py @@ -3,46 +3,93 @@ from backend.script_groups.base_script import BaseScript import psutil import json from datetime import datetime +from pathlib import Path class SystemInfo(BaseScript): """ - System Information - Collects and displays basic system information + 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(), - "usage": psutil.cpu_percent(interval=1), + "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": psutil.virtual_memory().total, - "available": psutil.virtual_memory().available, - "percent": psutil.virtual_memory().percent, + "total": memory.total, + "available": memory.available, + "used": memory.used, + "percent": memory.percent }, "disk": { - "total": psutil.disk_usage(work_dir).total, - "free": psutil.disk_usage(work_dir).free, - "percent": psutil.disk_usage(work_dir).percent, + "total": disk.total, + "used": disk.used, + "free": disk.free, + "percent": disk.percent }, - "timestamp": datetime.now().isoformat() + "network": { + "interfaces": list(psutil.net_if_addrs().keys()), + "connections": len(psutil.net_connections()) + } } - # Save to work directory if configured - config = self.get_config(work_dir, "example_group") - if config.get("save_system_info", False): - output_file = Path(work_dir) / "system_info.json" - with open(output_file, 'w') as f: - json.dump(info, f, indent=2) + # 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']}% usage) -Memory: {info['memory']['percent']}% used -Disk: {info['disk']['percent']}% used""" +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", @@ -54,4 +101,4 @@ Disk: {info['disk']['percent']}% used""" return { "status": "error", "error": str(e) - } + } \ No newline at end of file diff --git a/claude_file_organizer.py b/claude_file_organizer.py new file mode 100644 index 0000000..2889dae --- /dev/null +++ b/claude_file_organizer.py @@ -0,0 +1,171 @@ +import os +import shutil +from pathlib import Path +import re + +class ClaudeProjectOrganizer: + def __init__(self): + self.source_dir = Path.cwd() + self.claude_dir = self.source_dir / 'claude' + self.file_mapping = {} + + def should_skip_directory(self, dir_name): + skip_dirs = {'.git', '__pycache__', 'venv', 'env', '.pytest_cache', '.vscode', 'claude'} + return dir_name in skip_dirs + + def get_comment_prefix(self, file_extension): + """Determina el prefijo de comentario según la extensión del archivo""" + comment_styles = { + '.py': '#', + '.js': '//', + '.css': '/*', + '.html': '', + } + 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/files.txt b/files.txt index 8b81891a1e9aaa87c1d6c1d27a358e07bb17c9e3..544d4ddfffaa148c243ddb521d0a3064ace85ccc 100644 GIT binary patch delta 212 zcmaDQGDC8M8{_0UCauXEn0Ph^G0tIT)SmpFMR#%@qrhYxR*lI9Oaee@H=yWhRl0&x&}B?klUFdwOkTzzF!>D7 zs#7cyli#ozfb5vWDKOcF9nKVD6q&q^g9E6_3TV5;0<{tJIM$TNIr6~-F3^|i!Ikh(na7Hl#0H5JImH+?% delta 152 zcmbOs`ATGi8{_5z#udzyI5{ROu&OaCOm=5A2a*ff41r`Kqco=i0|drQzRIdQS&LI( zvJaaan684-E7-Ka{0v4BFwMpe;eTM}m<+-J>;_ { +async function initializeApp() { try { - await loadProfiles(); - await loadScriptGroups(); + console.log('Initializing app...'); + + // 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('Initialization error:', error); + console.error('Error initializing app:', error); showError('Failed to initialize 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 async function apiRequest(endpoint, options = {}) { @@ -39,40 +108,69 @@ async function apiRequest(endpoint, options = {}) { } } -// Profile functions async function loadProfiles() { try { const profiles = await apiRequest('/profiles'); updateProfileSelector(profiles); - // Select first profile if none selected - if (!currentProfile) { - const defaultProfile = profiles.find(p => p.id === 'default') || profiles[0]; - if (defaultProfile) { - await selectProfile(defaultProfile.id); - } + // 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('Failed to load profiles'); } } +async function selectProfile(profileId) { + try { + console.log('Selecting profile:', 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 in selectProfile:', error); + showError('Failed to load profile'); + } +} + +// 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(''); -} - -async function selectProfile(profileId) { - try { - currentProfile = await apiRequest(`/profiles/${profileId}`); - updateWorkDirDisplay(); - } catch (error) { - showError('Failed to load profile'); - } + + // Asegurar que el valor seleccionado sea correcto + select.value = lastProfileId; + console.log('Set profileSelect value to:', lastProfileId); } async function changeProfile() { diff --git a/frontend/static/js/modal.js b/frontend/static/js/modal.js new file mode 100644 index 0000000..f83a0eb --- /dev/null +++ b/frontend/static/js/modal.js @@ -0,0 +1,38 @@ +// 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/frontend/static/js/profile.js b/frontend/static/js/profile.js index 07e4417..0a3d18b 100644 --- a/frontend/static/js/profile.js +++ b/frontend/static/js/profile.js @@ -1,4 +1,4 @@ -// frontend/static/js/profile.js +let selectedProfileId = localStorage.getItem('selectedProfileId') || 'default'; // Profile functions async function loadProfiles() { @@ -6,13 +6,19 @@ async function loadProfiles() { const profiles = await apiRequest('/profiles'); updateProfileSelector(profiles); - // Select first profile if none selected - if (!currentProfile) { - const defaultProfile = profiles.find(p => p.id === 'default') || profiles[0]; - if (defaultProfile) { - await selectProfile(defaultProfile.id); - } + // Asegurarse de que se seleccione el perfil guardado + const selectElement = document.getElementById('profileSelect'); + if (profiles[selectedProfileId]) { + selectElement.value = selectedProfileId; + await selectProfile(selectedProfileId); // Cargar el perfil seleccionado + } else { + // Si el perfil guardado ya no existe, usar el default + selectedProfileId = 'default'; + selectElement.value = 'default'; + await selectProfile('default'); } + + localStorage.setItem('selectedProfileId', selectedProfileId); } catch (error) { showError('Failed to load profiles'); } @@ -156,14 +162,94 @@ async function saveProfile(event) { } } -function editProfile() { +// static/js/profile.js +async function editProfile() { if (!currentProfile) { showError('No profile selected'); return; } - showProfileEditor(currentProfile); + + 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 { + await apiRequest(`/profiles/${currentProfile.id}`, { + method: 'PUT', + body: JSON.stringify(profileData) + }); + + await loadProfiles(); + closeModal(modal.querySelector('button')); + showSuccess('Profile updated successfully'); + } catch (error) { + showError('Failed to update profile'); + } } function newProfile() { showProfileEditor(); +} + +async function onProfileChange(event) { + selectedProfileId = event.target.value; + localStorage.setItem('selectedProfileId', selectedProfileId); + await selectProfile(selectedProfileId); } \ No newline at end of file diff --git a/frontend/static/js/scripts.js b/frontend/static/js/scripts.js index 6450434..cf1bb0f 100644 --- a/frontend/static/js/scripts.js +++ b/frontend/static/js/scripts.js @@ -13,52 +13,228 @@ document.addEventListener('DOMContentLoaded', async () => { await loadScriptGroups(); }); -// Load available script groups 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('Failed to load script groups:', error); showError('Failed to load script groups'); } } -// Load scripts for selected group + +// 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 = ` +
+
+
+

+ Please select a work directory first +

+
+
+
+ `; + scriptList.style.display = 'block'; return; } try { - const scripts = await apiRequest(`/script-groups/${groupId}/scripts`); + 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; + } - scriptList.innerHTML = scripts.map(script => ` -
-
-

${script.name}

-

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

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

${script.name || script.id}

+

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

+
+ +
+
+ `).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) { - showError('Failed to load scripts for group'); + console.error('Error in loadGroupScripts:', error); + showError('Failed to load scripts and configuration'); } } @@ -172,18 +348,443 @@ function showGroupConfigEditor(groupId, config) { document.body.appendChild(modal); } -// Save group configuration -async function saveGroupConfig(event, groupId) { - event.preventDefault(); +function generateFormField(key, field, value) { + const currentValue = value !== undefined ? value : field.default; + + switch (field.type) { + case 'string': + return ` + + `; + case 'number': + return ` + + `; + case 'boolean': + return ` +
+ + Enable +
+ `; + 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 = ` +
+
+
+

+ Please select a work directory first +

+
+
+
+ `; + scriptList.style.display = 'block'; + return; + } try { - const configText = document.getElementById('configData').value; - const config = JSON.parse(configText); + 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 || 'Configuration'} +

+

${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 || 'No description available'}

+
+ +
+
+ `).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); + }); - await updateGroupConfig(groupId, config); - closeModal(event.target); - showSuccess('Group configuration updated successfully'); } catch (error) { - showError(`Failed to save configuration: ${error.message}`); + 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 = ` +
+

Edit Schema Configuration

+ +
+
+
+
+
+ + +
+
+ +
+
+
+ + +
+
+
+

Parameters

+
+ ${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('Failed to load configuration schema'); + } +} + +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'); } } \ No newline at end of file diff --git a/frontend/static/js/workdir_config.js b/frontend/static/js/workdir_config.js index 6a8bb6c..e960845 100644 --- a/frontend/static/js/workdir_config.js +++ b/frontend/static/js/workdir_config.js @@ -52,42 +52,43 @@ async function updateGroupConfig(groupId, settings) { } } +// static/js/workdir_config.js async function showWorkDirConfig() { if (!currentProfile?.work_dir) { showError('No work directory selected'); return; } - const config = await getWorkDirConfig(); - if (config) { - const modal = document.createElement('div'); - modal.className = 'modal active'; + try { + const config = await getWorkDirConfig(); - modal.innerHTML = ` -