Funcionando
This commit is contained in:
parent
3353b45424
commit
dbf4d9d685
|
@ -1,21 +1,35 @@
|
|||
# backend/app.py
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add the backend directory to Python path
|
||||
backend_dir = Path(__file__).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 core.directory_handler import select_directory
|
||||
from pathlib import Path
|
||||
from core.script_manager import ScriptManager
|
||||
from core.profile_manager import ProfileManager
|
||||
|
||||
app = Flask(__name__,
|
||||
template_folder='../frontend/templates',
|
||||
static_folder='../frontend/static')
|
||||
|
||||
# Initialize profile manager
|
||||
profile_manager = ProfileManager(Path('../data'))
|
||||
# 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"""
|
||||
|
@ -58,6 +72,7 @@ def delete_profile(profile_id):
|
|||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
|
||||
# Directory handling endpoints
|
||||
@app.route('/api/select-directory', methods=['GET'])
|
||||
def handle_select_directory():
|
||||
"""Handle directory selection"""
|
||||
|
@ -66,5 +81,61 @@ def handle_select_directory():
|
|||
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/<group_id>/<script_id>/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/<path:work_dir>', 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/<path:work_dir>/group/<group_id>', 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/<path:work_dir>/group/<group_id>', 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
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, port=5000)
|
||||
app.run(debug=True, port=5000)
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,112 @@
|
|||
# backend/core/script_manager.py
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
import inspect
|
||||
from typing import Dict, List, Any, Optional
|
||||
import json
|
||||
|
||||
class ScriptManager:
|
||||
"""Manages script discovery and execution"""
|
||||
|
||||
def __init__(self, script_groups_dir: Path):
|
||||
self.script_groups_dir = script_groups_dir
|
||||
|
||||
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:
|
||||
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),
|
||||
"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, work_dir: str,
|
||||
profile: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Execute a specific script"""
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
# 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}")
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,49 @@
|
|||
# backend/script_groups/example_group/x1.py
|
||||
from ..base_script import BaseScript
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
class FileCounter(BaseScript):
|
||||
"""
|
||||
Count Files in Directory
|
||||
Lists and counts files in the working directory by extension
|
||||
"""
|
||||
|
||||
def run(self, work_dir: str, profile: dict) -> dict:
|
||||
try:
|
||||
# Get configuration if any
|
||||
config = self.get_config(work_dir, "example_group")
|
||||
exclude_dirs = config.get("exclude_dirs", [])
|
||||
|
||||
# Initialize counters
|
||||
extension_counts = {}
|
||||
total_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:
|
||||
total_files += 1
|
||||
ext = Path(file).suffix.lower() or 'no extension'
|
||||
extension_counts[ext] = extension_counts.get(ext, 0) + 1
|
||||
|
||||
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"
|
||||
for ext, count in sorted(extension_counts.items())
|
||||
)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# backend/script_groups/example_group/x2.py
|
||||
from ..base_script import BaseScript
|
||||
import psutil
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
class SystemInfo(BaseScript):
|
||||
"""
|
||||
System Information
|
||||
Collects and displays basic system information
|
||||
"""
|
||||
|
||||
def run(self, work_dir: str, profile: dict) -> dict:
|
||||
try:
|
||||
# Collect system information
|
||||
info = {
|
||||
"cpu": {
|
||||
"cores": psutil.cpu_count(),
|
||||
"usage": psutil.cpu_percent(interval=1),
|
||||
},
|
||||
"memory": {
|
||||
"total": psutil.virtual_memory().total,
|
||||
"available": psutil.virtual_memory().available,
|
||||
"percent": psutil.virtual_memory().percent,
|
||||
},
|
||||
"disk": {
|
||||
"total": psutil.disk_usage(work_dir).total,
|
||||
"free": psutil.disk_usage(work_dir).free,
|
||||
"percent": psutil.disk_usage(work_dir).percent,
|
||||
},
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# 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)
|
||||
|
||||
# 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"""
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"data": info,
|
||||
"output": output
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"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": "D:/Proyectos/AutoCAD",
|
||||
"llm_settings": {
|
||||
"api_key": "333333333333",
|
||||
"model": "gpt-4",
|
||||
"temperature": 0.7
|
||||
},
|
||||
"created_at": "2025-02-07T13:00:43.541932",
|
||||
"updated_at": "2025-02-07T13:01:40.473406"
|
||||
}
|
||||
}
|
|
@ -1,113 +1,163 @@
|
|||
# frontend/static/css/style.css
|
||||
:root {
|
||||
--primary-color: #2c3e50;
|
||||
--secondary-color: #34495e;
|
||||
--accent-color: #3498db;
|
||||
--text-color: #333;
|
||||
--bg-color: #f5f6fa;
|
||||
--border-color: #dcdde1;
|
||||
}
|
||||
/* frontend/static/css/style.css - Add these styles */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
.script-group {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
nav {
|
||||
.script-group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
.script-group-header h3 {
|
||||
margin: 0;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.profile-selector {
|
||||
.script-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
select, input, button {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.work-dir-section {
|
||||
background: white;
|
||||
.script-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
background: var(--bg-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.work-dir-controls {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.work-dir-controls input {
|
||||
.script-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.scripts-section {
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
.script-info h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.script-groups {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
.script-info p {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.script-group {
|
||||
background: var(--bg-color);
|
||||
padding: 1rem;
|
||||
.script-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.config-btn {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.run-btn {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
|
||||
.run-btn:hover {
|
||||
background-color: #219a52;
|
||||
}
|
||||
|
||||
.config-editor {
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.script
|
||||
.output-area {
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.no-scripts {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Add to your existing style.css */
|
||||
|
||||
.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: 8px;
|
||||
max-width: 600px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.config-info {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.group-configs {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.group-config {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background: var(--bg-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.group-config pre {
|
||||
margin-top: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.9rem;
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
# frontend/static/js/main.js
|
||||
// frontend/static/js/main.js
|
||||
|
||||
// Global state
|
||||
let currentProfile = null;
|
||||
|
||||
// Initialize when page loads
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadProfiles();
|
||||
updateWorkDirDisplay();
|
||||
try {
|
||||
await loadProfiles();
|
||||
await loadScriptGroups();
|
||||
updateWorkDirDisplay();
|
||||
} catch (error) {
|
||||
console.error('Initialization error:', error);
|
||||
showError('Failed to initialize application');
|
||||
}
|
||||
});
|
||||
|
||||
// API functions
|
||||
|
@ -70,13 +77,18 @@ async function selectProfile(profileId) {
|
|||
|
||||
async function changeProfile() {
|
||||
const select = document.getElementById('profileSelect');
|
||||
await selectProfile(select.value);
|
||||
if (select.value) {
|
||||
await selectProfile(select.value);
|
||||
await loadScriptGroups(); // Reload scripts when profile changes
|
||||
}
|
||||
}
|
||||
|
||||
// Work directory functions
|
||||
function updateWorkDirDisplay() {
|
||||
const input = document.getElementById('workDirPath');
|
||||
input.value = currentProfile?.work_dir || '';
|
||||
if (input && currentProfile) {
|
||||
input.value = currentProfile.work_dir || '';
|
||||
}
|
||||
}
|
||||
|
||||
async function selectWorkDir() {
|
||||
|
@ -101,17 +113,39 @@ async function selectWorkDir() {
|
|||
// Output functions
|
||||
function showError(message) {
|
||||
const output = document.getElementById('outputArea');
|
||||
output.innerHTML += `\nError: ${message}`;
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
output.innerHTML += `\n[${timestamp}] ERROR: ${message}`;
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
const output = document.getElementById('outputArea');
|
||||
output.innerHTML += `\nSuccess: ${message}`;
|
||||
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;
|
|
@ -1,4 +1,65 @@
|
|||
# frontend/static/js/profile.js
|
||||
// frontend/static/js/profile.js
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showError('Failed to load profiles');
|
||||
}
|
||||
}
|
||||
|
||||
function updateProfileSelector(profiles) {
|
||||
const select = document.getElementById('profileSelect');
|
||||
select.innerHTML = profiles.map(profile => `
|
||||
<option value="${profile.id}" ${profile.id === currentProfile?.id ? 'selected' : ''}>
|
||||
${profile.name}
|
||||
</option>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
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
|
||||
let editingProfile = null;
|
||||
|
||||
|
@ -27,7 +88,7 @@ function showProfileEditor(profile = null) {
|
|||
<div class="form-group">
|
||||
<label for="workDir">Work Directory</label>
|
||||
<input type="text" id="workDir" name="work_dir"
|
||||
value="${profile?.work_dir || ''}" required>
|
||||
value="${profile?.work_dir || ''}" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="llmModel">LLM Model</label>
|
||||
|
@ -48,7 +109,7 @@ function showProfileEditor(profile = null) {
|
|||
min="0" max="2" step="0.1">
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button type="button" onclick="closeProfileEditor()">Cancel</button>
|
||||
<button type="button" onclick="closeModal(this)">Cancel</button>
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -58,14 +119,6 @@ function showProfileEditor(profile = null) {
|
|||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
function closeProfileEditor() {
|
||||
const modal = document.querySelector('.modal');
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
}
|
||||
editingProfile = null;
|
||||
}
|
||||
|
||||
async function saveProfile(event) {
|
||||
event.preventDefault();
|
||||
const form = event.target;
|
||||
|
@ -96,7 +149,7 @@ async function saveProfile(event) {
|
|||
}
|
||||
|
||||
await loadProfiles();
|
||||
closeProfileEditor();
|
||||
closeModal(event.target);
|
||||
showSuccess(`Profile ${editingProfile ? 'updated' : 'created'} successfully`);
|
||||
} catch (error) {
|
||||
showError(`Failed to ${editingProfile ? 'update' : 'create'} profile`);
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
// 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 from API
|
||||
async function loadScriptGroups() {
|
||||
try {
|
||||
scriptGroups = await apiRequest('/scripts');
|
||||
updateScriptGroupsDisplay();
|
||||
} catch (error) {
|
||||
showError('Failed to load script groups');
|
||||
}
|
||||
}
|
||||
|
||||
// Update script groups display
|
||||
function updateScriptGroupsDisplay() {
|
||||
const container = document.getElementById('scriptGroups');
|
||||
|
||||
if (!scriptGroups.length) {
|
||||
container.innerHTML = '<p class="no-scripts">No script groups available</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = scriptGroups.map(group => `
|
||||
<div class="script-group" data-group-id="${group.id}">
|
||||
<div class="script-group-header">
|
||||
<h3>${group.name}</h3>
|
||||
<button onclick="configureGroup('${group.id}')" class="config-btn">
|
||||
Configure
|
||||
</button>
|
||||
</div>
|
||||
<div class="script-list">
|
||||
${group.scripts.map(script => `
|
||||
<div class="script-item">
|
||||
<div class="script-info">
|
||||
<h4>${script.name}</h4>
|
||||
<p>${script.description || 'No description available'}</p>
|
||||
</div>
|
||||
<div class="script-actions">
|
||||
<button onclick="runScript('${group.id}', '${script.id}')" class="run-btn">
|
||||
Run
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).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 = `
|
||||
<div class="modal-content">
|
||||
<h2>${group.name} Configuration</h2>
|
||||
<form id="groupConfigForm" onsubmit="saveGroupConfig(event, '${groupId}')">
|
||||
<div class="form-group">
|
||||
<label for="configData">Configuration (JSON)</label>
|
||||
<textarea id="configData" name="config" rows="10"
|
||||
class="config-editor">${JSON.stringify(config || {}, null, 2)}</textarea>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button type="button" onclick="closeModal(this)">Cancel</button>
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
// Save group configuration
|
||||
async function saveGroupConfig(event, groupId) {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
const configText = document.getElementById('configData').value;
|
||||
const config = JSON.parse(configText);
|
||||
|
||||
await updateGroupConfig(groupId, config);
|
||||
closeModal(event.target);
|
||||
showSuccess('Group configuration updated successfully');
|
||||
} catch (error) {
|
||||
showError(`Failed to save configuration: ${error.message}`);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
# frontend/static/js/workdir_config.js
|
||||
// frontend/static/js/workdir_config.js
|
||||
|
||||
async function getWorkDirConfig() {
|
||||
if (!currentProfile?.work_dir) {
|
||||
showError('No work directory selected');
|
||||
|
@ -51,44 +52,43 @@ async function updateGroupConfig(groupId, settings) {
|
|||
}
|
||||
}
|
||||
|
||||
function showWorkDirConfig() {
|
||||
async function showWorkDirConfig() {
|
||||
if (!currentProfile?.work_dir) {
|
||||
showError('No work directory selected');
|
||||
return;
|
||||
}
|
||||
|
||||
getWorkDirConfig().then(config => {
|
||||
if (config) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'modal active';
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<h2>Work Directory Configuration</h2>
|
||||
<div class="config-info">
|
||||
<p><strong>Directory:</strong> ${currentProfile.work_dir}</p>
|
||||
<p><strong>Version:</strong> ${config.version}</p>
|
||||
<p><strong>Created:</strong> ${new Date(config.created_at).toLocaleString()}</p>
|
||||
<p><strong>Updated:</strong> ${new Date(config.updated_at).toLocaleString()}</p>
|
||||
</div>
|
||||
<div class="group-configs">
|
||||
<h3>Group Configurations</h3>
|
||||
${Object.entries(config.group_settings || {}).map(([groupId, settings]) => `
|
||||
<div class="group-config">
|
||||
<h4>${groupId}</h4>
|
||||
<pre>${JSON.stringify(settings, null, 2)}</pre>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button onclick="closeModal(this)">Close</button>
|
||||
</div>
|
||||
const config = await getWorkDirConfig();
|
||||
if (config) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'modal active';
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<h2>Work Directory Configuration</h2>
|
||||
<div class="config-info">
|
||||
<p><strong>Directory:</strong> ${currentProfile.work_dir}</p>
|
||||
<p><strong>Version:</strong> ${config.version}</p>
|
||||
<p><strong>Created:</strong> ${new Date(config.created_at).toLocaleString()}</p>
|
||||
<p><strong>Updated:</strong> ${new Date(config.updated_at).toLocaleString()}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
});
|
||||
<div class="group-configs">
|
||||
<h3>Group Configurations</h3>
|
||||
${Object.entries(config.group_settings || {}).map(([groupId, settings]) => `
|
||||
<div class="group-config">
|
||||
<h4>${groupId}</h4>
|
||||
<pre>${JSON.stringify(settings, null, 2)}</pre>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button onclick="closeModal(this)">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(button) {
|
||||
|
|
|
@ -1,35 +1,59 @@
|
|||
# frontend/templates/index.html
|
||||
{% extends "base.html" %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Local Scripts Web - Home</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<div class="nav-brand">Local Scripts Web</div>
|
||||
<div class="profile-selector">
|
||||
<select id="profileSelect" onchange="changeProfile()">
|
||||
<option value="">Loading profiles...</option>
|
||||
</select>
|
||||
<button onclick="editProfile()">Edit Profile</button>
|
||||
<button onclick="newProfile()">New Profile</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{% block title %}Local Scripts Web - Home{% endblock %}
|
||||
<main>
|
||||
<div class="container">
|
||||
<div class="work-dir-section">
|
||||
<h2>Work Directory</h2>
|
||||
<div class="work-dir-controls">
|
||||
<input type="text" id="workDirPath" readonly>
|
||||
<button onclick="selectWorkDir()">Browse</button>
|
||||
<button onclick="showWorkDirConfig()">Config</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="work-dir-section">
|
||||
<h2>Work Directory</h2>
|
||||
<div class="work-dir-controls">
|
||||
<input type="text" id="workDirPath" readonly>
|
||||
<button onclick="selectWorkDir()">Browse</button>
|
||||
<button onclick="editWorkDirConfig()">Config</button>
|
||||
<div class="scripts-section">
|
||||
<h2>Available Scripts</h2>
|
||||
<div id="scriptGroups" class="script-groups">
|
||||
<!-- Script groups will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h2>Output</h2>
|
||||
<div class="output-controls">
|
||||
<button onclick="clearOutput()">Clear Output</button>
|
||||
</div>
|
||||
<div id="outputArea" class="output-area">
|
||||
<!-- Script output will appear here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="scripts-section">
|
||||
<h2>Available Scripts</h2>
|
||||
<div id="scriptGroups" class="script-groups">
|
||||
<!-- Script groups will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h2>Output</h2>
|
||||
<div id="outputArea" class="output-area">
|
||||
<!-- Script output will appear here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="{{ url_for('static', filename='js/profile.js') }}"></script>
|
||||
{% endblock %}
|
||||
<!-- Scripts -->
|
||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/workdir_config.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/profile.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/scripts.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue