1251 lines
45 KiB
JavaScript
1251 lines
45 KiB
JavaScript
// Python Launcher - Gestión de proyectos Python sin GUI (MCP servers, Flask apps, etc.)
|
||
|
||
// Variables globales para Python launcher
|
||
let pythonProjects = [];
|
||
let pythonScripts = [];
|
||
let pythonFavorites = [];
|
||
let pythonRunningProcesses = [];
|
||
let pythonHistory = [];
|
||
let selectedPythonProject = null;
|
||
let pythonCategories = {};
|
||
let currentPythonFilter = 'all';
|
||
let pythonMarkdownFiles = [];
|
||
let currentEditingPythonProject = null;
|
||
let pythonEnvs = [];
|
||
|
||
// === INICIALIZACIÓN ===
|
||
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
console.log('Python Launcher: DOM loaded');
|
||
});
|
||
|
||
// Función llamada cuando se activa el tab Python
|
||
function initPythonLauncher() {
|
||
console.log('Python Launcher: Initializing...');
|
||
loadPythonEnvironments();
|
||
loadPythonProjects();
|
||
loadPythonCategories();
|
||
loadPythonFavorites();
|
||
loadPythonHistory();
|
||
refreshPythonProcesses();
|
||
|
||
// Actualizar procesos cada 30 segundos
|
||
setInterval(refreshPythonProcesses, 30000);
|
||
}
|
||
|
||
// === GESTIÓN DE ENTORNOS PYTHON ===
|
||
|
||
async function loadPythonEnvironments() {
|
||
try {
|
||
const response = await fetch('/api/python-environments');
|
||
if (response.ok) {
|
||
pythonEnvs = await response.json();
|
||
renderPythonEnvSelector();
|
||
console.log('Python environments loaded:', pythonEnvs.length);
|
||
} else {
|
||
console.error('Error loading Python environments:', response.statusText);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python environments:', error);
|
||
}
|
||
}
|
||
|
||
function renderPythonEnvSelector() {
|
||
const selector = document.getElementById('python-project-python-env');
|
||
if (!selector) return;
|
||
|
||
selector.innerHTML = '';
|
||
pythonEnvs.forEach(env => {
|
||
const option = document.createElement('option');
|
||
option.value = env.name;
|
||
option.textContent = env.display_name;
|
||
selector.appendChild(option);
|
||
});
|
||
}
|
||
|
||
// === GESTIÓN DE PROYECTOS ===
|
||
|
||
async function loadPythonProjects() {
|
||
try {
|
||
const response = await fetch('/api/python-projects');
|
||
if (response.ok) {
|
||
pythonProjects = await response.json();
|
||
updatePythonProjectSelector();
|
||
console.log('Python projects loaded:', pythonProjects.length);
|
||
} else {
|
||
console.error('Error loading Python projects:', response.statusText);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python projects:', error);
|
||
}
|
||
}
|
||
|
||
function updatePythonProjectSelector() {
|
||
const selector = document.getElementById('python-project-select');
|
||
if (!selector) return;
|
||
|
||
// Guardar la selección actual
|
||
const currentSelection = getCurrentPythonProjectSelection();
|
||
|
||
// Limpiar opciones existentes
|
||
selector.innerHTML = '<option value="">-- Seleccionar Proyecto --</option>';
|
||
|
||
// Ordenar proyectos alfabéticamente por nombre
|
||
const sortedProjects = [...pythonProjects].sort((a, b) => a.name.localeCompare(b.name));
|
||
|
||
// Agregar proyectos
|
||
sortedProjects.forEach(project => {
|
||
const option = document.createElement('option');
|
||
option.value = project.id;
|
||
option.textContent = project.name;
|
||
option.dataset.description = project.description || '';
|
||
option.dataset.category = project.category || 'Otros';
|
||
option.dataset.pythonEnv = project.python_env || 'base';
|
||
selector.appendChild(option);
|
||
});
|
||
|
||
// Restaurar la selección guardada
|
||
restorePythonProjectSelection(currentSelection);
|
||
}
|
||
|
||
// Función para obtener la selección actual del localStorage
|
||
function getCurrentPythonProjectSelection() {
|
||
const currentValue = document.getElementById('python-project-select')?.value;
|
||
return localStorage.getItem('python-selected-project') || currentValue || '';
|
||
}
|
||
|
||
// Función para restaurar la selección desde localStorage
|
||
function restorePythonProjectSelection(projectId) {
|
||
const selector = document.getElementById('python-project-select');
|
||
if (!selector || !projectId) return;
|
||
|
||
// Verificar que el proyecto aún existe
|
||
const projectExists = pythonProjects.some(p => p.id === projectId);
|
||
if (projectExists) {
|
||
selector.value = projectId;
|
||
// Cargar scripts del proyecto restaurado automáticamente
|
||
loadPythonScripts();
|
||
} else {
|
||
// Si el proyecto ya no existe, limpiar localStorage
|
||
localStorage.removeItem('python-selected-project');
|
||
}
|
||
}
|
||
|
||
async function loadPythonScripts() {
|
||
const projectId = document.getElementById('python-project-select').value;
|
||
|
||
// Guardar la selección en localStorage
|
||
if (projectId) {
|
||
localStorage.setItem('python-selected-project', projectId);
|
||
} else {
|
||
localStorage.removeItem('python-selected-project');
|
||
}
|
||
|
||
if (!projectId) {
|
||
selectedPythonProject = null;
|
||
pythonScripts = [];
|
||
pythonMarkdownFiles = [];
|
||
updatePythonScriptsGrid();
|
||
updatePythonProjectButtons();
|
||
renderPythonMarkdownFiles();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Cargar scripts y archivos markdown en paralelo
|
||
const [scriptsResponse, markdownResponse] = await Promise.all([
|
||
fetch(`/api/python-scripts/${projectId}`),
|
||
fetch(`/api/python-markdown/${projectId}`)
|
||
]);
|
||
|
||
if (scriptsResponse.ok) {
|
||
pythonScripts = await scriptsResponse.json();
|
||
selectedPythonProject = pythonProjects.find(p => p.id === projectId);
|
||
updatePythonScriptsGrid();
|
||
updatePythonProjectButtons();
|
||
console.log('Python scripts loaded:', pythonScripts.length);
|
||
} else {
|
||
console.error('Error loading Python scripts:', scriptsResponse.statusText);
|
||
pythonScripts = [];
|
||
updatePythonScriptsGrid();
|
||
}
|
||
|
||
if (markdownResponse.ok) {
|
||
const markdownData = await markdownResponse.json();
|
||
pythonMarkdownFiles = markdownData.files || [];
|
||
renderPythonMarkdownFiles();
|
||
console.log('Python markdown files loaded:', pythonMarkdownFiles.length);
|
||
} else {
|
||
pythonMarkdownFiles = [];
|
||
renderPythonMarkdownFiles();
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python project content:', error);
|
||
pythonScripts = [];
|
||
pythonMarkdownFiles = [];
|
||
updatePythonScriptsGrid();
|
||
renderPythonMarkdownFiles();
|
||
}
|
||
}
|
||
|
||
function updatePythonProjectButtons() {
|
||
const buttonsToShow = ['vscode-python-btn', 'cursor-python-btn', 'folder-python-btn', 'copy-path-python-btn', 'manage-python-scripts-btn'];
|
||
const hasProject = selectedPythonProject !== null;
|
||
|
||
buttonsToShow.forEach(buttonId => {
|
||
const button = document.getElementById(buttonId);
|
||
if (button) {
|
||
button.style.display = hasProject ? 'block' : 'none';
|
||
}
|
||
});
|
||
|
||
// Actualizar icono del proyecto
|
||
const projectIcon = document.getElementById('selected-python-project-icon');
|
||
if (projectIcon && selectedPythonProject) {
|
||
const categoryIcon = pythonCategories[selectedPythonProject.category]?.icon || '🐍';
|
||
projectIcon.textContent = categoryIcon;
|
||
}
|
||
}
|
||
|
||
// === GESTIÓN DE SCRIPTS ===
|
||
|
||
function updatePythonScriptsGrid() {
|
||
const grid = document.getElementById('python-scripts-grid');
|
||
if (!grid) return;
|
||
|
||
grid.innerHTML = '';
|
||
|
||
if (!selectedPythonProject || pythonScripts.length === 0) {
|
||
grid.innerHTML = `
|
||
<div class="col-span-full text-center text-gray-500 py-8">
|
||
${!selectedPythonProject ?
|
||
'Selecciona un proyecto para ver sus scripts' :
|
||
'No hay scripts disponibles en este proyecto'
|
||
}
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
// Filtrar scripts por categoría
|
||
const filteredScripts = currentPythonFilter === 'all' ?
|
||
pythonScripts :
|
||
pythonScripts.filter(script => {
|
||
const scriptCategory = getScriptCategory(script);
|
||
return scriptCategory === currentPythonFilter;
|
||
});
|
||
|
||
filteredScripts.forEach(script => {
|
||
const scriptCard = createPythonScriptCard(script);
|
||
grid.appendChild(scriptCard);
|
||
});
|
||
|
||
if (filteredScripts.length === 0) {
|
||
grid.innerHTML = `
|
||
<div class="col-span-full text-center text-gray-500 py-8">
|
||
No hay scripts que coincidan con el filtro seleccionado
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
function createPythonScriptCard(script) {
|
||
const card = document.createElement('div');
|
||
card.className = 'bg-white border rounded-lg p-4 hover:shadow-md transition-shadow';
|
||
|
||
const isFavorite = pythonFavorites.some(fav =>
|
||
fav.project_id === selectedPythonProject.id && fav.script_name === script.filename
|
||
);
|
||
|
||
const scriptCategory = getScriptCategory(script);
|
||
const categoryInfo = pythonCategories[scriptCategory] || pythonCategories['Otros'];
|
||
|
||
// Determinar si es un servidor o script de background
|
||
const isServerScript = script.is_server || script.requires_background;
|
||
const serverInfo = script.server_port ? ` (Puerto: ${script.server_port})` : '';
|
||
|
||
card.innerHTML = `
|
||
<div class="flex justify-between items-start mb-2">
|
||
<h3 class="font-semibold text-lg">${script.display_name}</h3>
|
||
<div class="flex items-center gap-1">
|
||
${script.tags.map(tag => `<span class="bg-gray-100 text-xs px-2 py-1 rounded">${tag}</span>`).join('')}
|
||
<button onclick="togglePythonFavorite('${selectedPythonProject.id}', '${script.filename}')"
|
||
class="text-xl hover:scale-110 transition-transform ml-2"
|
||
title="${isFavorite ? 'Quitar de favoritos' : 'Agregar a favoritos'}">
|
||
${isFavorite ? '⭐' : '☆'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center mb-2">
|
||
<span class="text-sm font-medium" style="color: ${categoryInfo.color}">
|
||
${categoryInfo.icon} ${scriptCategory}
|
||
</span>
|
||
${isServerScript ? `<span class="ml-2 bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">SERVER${serverInfo}</span>` : ''}
|
||
</div>
|
||
|
||
<p class="text-gray-600 text-sm mb-3">${script.description || 'Sin descripción'}</p>
|
||
|
||
<div class="flex justify-between items-center">
|
||
<div class="text-xs text-gray-500">
|
||
📄 ${script.filename}
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button onclick="showPythonScriptDescription('${script.filename}', '${script.display_name}')"
|
||
class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600"
|
||
title="Ver descripción detallada">
|
||
ℹ️
|
||
</button>
|
||
<button onclick="showPythonScriptOptions('${script.filename}')"
|
||
class="bg-purple-500 text-white px-3 py-1 rounded text-sm hover:bg-purple-600"
|
||
title="Ejecutar con opciones">
|
||
⚙️
|
||
</button>
|
||
${isServerScript ? `
|
||
<button onclick="executePythonScript('${script.filename}', [], true)"
|
||
class="bg-green-500 text-white px-3 py-1 rounded text-sm hover:bg-green-600"
|
||
title="Ejecutar como servidor (background)">
|
||
🔄 Servidor
|
||
</button>
|
||
` : ''}
|
||
<button onclick="executePythonScript('${script.filename}', [], ${isServerScript})"
|
||
class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600"
|
||
title="Ejecutar script">
|
||
▶️ Ejecutar
|
||
</button>
|
||
<button onclick="showPythonScriptOptions('${script.filename}')"
|
||
class="bg-gray-500 text-white px-3 py-1 rounded text-sm hover:bg-gray-600"
|
||
title="Opciones avanzadas">
|
||
⚙️
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
return card;
|
||
}
|
||
|
||
function getScriptCategory(script) {
|
||
// Categorización automática basada en tags o nombres
|
||
const filename = script.filename.toLowerCase();
|
||
const tags = script.tags.map(tag => tag.toLowerCase());
|
||
|
||
if (tags.includes('mcp') || filename.includes('mcp') || script.is_server) {
|
||
return 'MCP Servers';
|
||
} else if (tags.includes('flask') || filename.includes('flask') || filename.includes('api')) {
|
||
return 'Flask Apps';
|
||
} else if (tags.includes('bot') || filename.includes('bot')) {
|
||
return 'Bots';
|
||
} else if (tags.includes('data') || filename.includes('data') || tags.includes('analysis')) {
|
||
return 'Data Processing';
|
||
} else if (script.requires_background) {
|
||
return 'Scripts';
|
||
}
|
||
|
||
return 'Scripts';
|
||
}
|
||
|
||
// === EJECUCIÓN DE SCRIPTS ===
|
||
|
||
async function executePythonScript(scriptName, args = [], runInBackground = false) {
|
||
if (!selectedPythonProject) {
|
||
alert('Selecciona un proyecto primero');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const payload = {
|
||
project_id: selectedPythonProject.id,
|
||
script_name: scriptName,
|
||
args: args,
|
||
working_dir: selectedPythonProject.directory,
|
||
run_in_background: runInBackground
|
||
};
|
||
|
||
const response = await fetch('/api/execute-python-script', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(payload)
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.error) {
|
||
alert(`Error: ${result.error}`);
|
||
} else {
|
||
console.log('Script execution result:', result);
|
||
// Actualizar historial y procesos
|
||
setTimeout(() => {
|
||
loadPythonHistory();
|
||
refreshPythonProcesses();
|
||
}, 1000);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error executing Python script:', error);
|
||
alert(`Error ejecutando script: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
function showPythonScriptOptions(scriptName) {
|
||
const script = pythonScripts.find(s => s.filename === scriptName);
|
||
if (!script) return;
|
||
|
||
const modal = document.getElementById('python-script-options-modal');
|
||
const scriptDisplayName = document.getElementById('python-script-display-name');
|
||
const scriptDescription = document.getElementById('python-script-description');
|
||
|
||
if (modal && scriptDisplayName && scriptDescription) {
|
||
scriptDisplayName.textContent = script.display_name;
|
||
scriptDescription.textContent = script.description || 'Script Python';
|
||
|
||
// Limpiar campos anteriores
|
||
document.getElementById('python-script-args-input').value = '';
|
||
|
||
// Configurar tipo de ejecución por defecto basado en el script
|
||
const normalRadio = document.querySelector('input[name="python-execution-type"][value="false"]');
|
||
const backgroundRadio = document.querySelector('input[name="python-execution-type"][value="true"]');
|
||
|
||
if (script.requires_background || script.is_server) {
|
||
backgroundRadio.checked = true;
|
||
} else {
|
||
normalRadio.checked = true;
|
||
}
|
||
|
||
modal.dataset.scriptName = scriptName;
|
||
modal.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
function closePythonScriptOptions() {
|
||
const modal = document.getElementById('python-script-options-modal');
|
||
if (modal) {
|
||
modal.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
function executePythonScriptWithOptions() {
|
||
const modal = document.getElementById('python-script-options-modal');
|
||
const argsInput = document.getElementById('python-script-args-input');
|
||
const executionTypeInputs = document.getElementsByName('python-execution-type');
|
||
|
||
if (modal && argsInput) {
|
||
const scriptName = modal.dataset.scriptName;
|
||
const args = argsInput.value.trim().split(/\s+/).filter(arg => arg.length > 0);
|
||
|
||
// Leer el tipo de ejecución seleccionado
|
||
let runInBackground = false;
|
||
for (const input of executionTypeInputs) {
|
||
if (input.checked) {
|
||
runInBackground = input.value === 'true';
|
||
break;
|
||
}
|
||
}
|
||
|
||
executePythonScript(scriptName, args, runInBackground);
|
||
closePythonScriptOptions();
|
||
}
|
||
}
|
||
|
||
// === FAVORITOS ===
|
||
|
||
async function loadPythonFavorites() {
|
||
try {
|
||
const response = await fetch('/api/python-favorites');
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
pythonFavorites = data.favorites || [];
|
||
updatePythonFavoritesPanel();
|
||
console.log('Python favorites loaded:', pythonFavorites.length);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python favorites:', error);
|
||
}
|
||
}
|
||
|
||
async function togglePythonFavorite(projectId, scriptName) {
|
||
try {
|
||
const response = await fetch('/api/python-favorites', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
project_id: projectId,
|
||
script_name: scriptName
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.error) {
|
||
alert(`Error: ${result.error}`);
|
||
} else {
|
||
console.log('Favorite toggled:', result.message);
|
||
loadPythonFavorites();
|
||
updatePythonScriptsGrid(); // Actualizar para mostrar cambio de estrella
|
||
}
|
||
} catch (error) {
|
||
console.error('Error toggling Python favorite:', error);
|
||
}
|
||
}
|
||
|
||
function updatePythonFavoritesPanel() {
|
||
const panel = document.getElementById('python-favorites-list');
|
||
const counter = document.getElementById('python-favorites-count');
|
||
|
||
if (!panel || !counter) return;
|
||
|
||
counter.textContent = `${pythonFavorites.length} favoritos`;
|
||
|
||
if (pythonFavorites.length === 0) {
|
||
panel.innerHTML = '<p class="text-gray-500 text-sm">No tienes scripts favoritos</p>';
|
||
return;
|
||
}
|
||
|
||
panel.innerHTML = '';
|
||
pythonFavorites.forEach(favorite => {
|
||
const favoriteItem = document.createElement('div');
|
||
favoriteItem.className = 'flex items-center justify-between bg-white p-3 rounded border';
|
||
favoriteItem.innerHTML = `
|
||
<div class="flex-1">
|
||
<div class="font-medium">${favorite.display_name}</div>
|
||
<div class="text-sm text-gray-500">
|
||
${favorite.project_name} • ${favorite.script_name}
|
||
</div>
|
||
<div class="text-xs text-gray-400">${favorite.description || 'Sin descripción'}</div>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button onclick="executePythonFavorite('${favorite.project_id}', '${favorite.script_name}')"
|
||
class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600">
|
||
▶️ Ejecutar
|
||
</button>
|
||
<button onclick="togglePythonFavorite('${favorite.project_id}', '${favorite.script_name}')"
|
||
class="text-red-500 hover:text-red-700 text-sm">
|
||
❌
|
||
</button>
|
||
</div>
|
||
`;
|
||
panel.appendChild(favoriteItem);
|
||
});
|
||
}
|
||
|
||
async function executePythonFavorite(projectId, scriptName) {
|
||
// Cambiar al proyecto correcto si es necesario
|
||
const currentProject = document.getElementById('python-project-select').value;
|
||
if (currentProject !== projectId) {
|
||
document.getElementById('python-project-select').value = projectId;
|
||
await loadPythonScripts();
|
||
}
|
||
|
||
// Ejecutar el script
|
||
executePythonScript(scriptName);
|
||
}
|
||
|
||
// === CATEGORÍAS Y FILTROS ===
|
||
|
||
async function loadPythonCategories() {
|
||
try {
|
||
const response = await fetch('/api/python-categories');
|
||
if (response.ok) {
|
||
pythonCategories = await response.json();
|
||
console.log('Python categories loaded:', Object.keys(pythonCategories).length);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python categories:', error);
|
||
}
|
||
}
|
||
|
||
function filterPythonByCategory(category) {
|
||
currentPythonFilter = category;
|
||
|
||
// Actualizar botones de filtro
|
||
document.querySelectorAll('.python-category-btn').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
if (btn.dataset.category === category) {
|
||
btn.classList.add('active');
|
||
}
|
||
});
|
||
|
||
updatePythonScriptsGrid();
|
||
}
|
||
|
||
// === PROCESOS EN EJECUCIÓN ===
|
||
|
||
async function refreshPythonProcesses() {
|
||
try {
|
||
const response = await fetch('/api/python-running-processes');
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
pythonRunningProcesses = data.processes || [];
|
||
updatePythonProcessesPanel();
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python processes:', error);
|
||
}
|
||
}
|
||
|
||
function updatePythonProcessesPanel() {
|
||
const panel = document.getElementById('python-running-processes');
|
||
if (!panel) return;
|
||
|
||
if (pythonRunningProcesses.length === 0) {
|
||
panel.innerHTML = '<p class="text-gray-500 text-sm">No hay procesos en ejecución</p>';
|
||
return;
|
||
}
|
||
|
||
panel.innerHTML = '';
|
||
pythonRunningProcesses.forEach(process => {
|
||
const processItem = document.createElement('div');
|
||
processItem.className = 'flex items-center justify-between bg-gray-50 p-3 rounded border';
|
||
|
||
const startTime = new Date(process.start_time).toLocaleString();
|
||
const isBackground = process.is_background;
|
||
|
||
processItem.innerHTML = `
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${process.script_name}
|
||
${isBackground ? '<span class="ml-2 bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">BACKGROUND</span>' : ''}
|
||
</div>
|
||
<div class="text-sm text-gray-600">
|
||
${process.project_name} • PID: ${process.pid}
|
||
</div>
|
||
<div class="text-xs text-gray-500">Iniciado: ${startTime}</div>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button onclick="focusPythonProcess(${process.pid})"
|
||
class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600"
|
||
title="Dar foco al proceso">
|
||
🔍
|
||
</button>
|
||
<button onclick="terminatePythonProcess(${process.pid})"
|
||
class="bg-red-500 text-white px-3 py-1 rounded text-sm hover:bg-red-600"
|
||
title="Terminar proceso">
|
||
❌
|
||
</button>
|
||
</div>
|
||
`;
|
||
panel.appendChild(processItem);
|
||
});
|
||
}
|
||
|
||
async function terminatePythonProcess(pid) {
|
||
if (!confirm(`¿Estás seguro de que quieres terminar el proceso ${pid}?`)) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/python-process-terminate/${pid}`, {
|
||
method: 'POST'
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.error) {
|
||
alert(`Error: ${result.error}`);
|
||
} else {
|
||
console.log('Process terminated:', result.message);
|
||
refreshPythonProcesses();
|
||
}
|
||
} catch (error) {
|
||
console.error('Error terminating Python process:', error);
|
||
}
|
||
}
|
||
|
||
async function focusPythonProcess(pid) {
|
||
try {
|
||
const response = await fetch(`/api/python-process-focus/${pid}`, {
|
||
method: 'POST'
|
||
});
|
||
|
||
const result = await response.json();
|
||
console.log('Focus result:', result.message);
|
||
} catch (error) {
|
||
console.error('Error focusing Python process:', error);
|
||
}
|
||
}
|
||
|
||
// === HISTORIAL ===
|
||
|
||
async function loadPythonHistory() {
|
||
try {
|
||
const response = await fetch('/api/python-history');
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
pythonHistory = data.history || [];
|
||
updatePythonHistoryPanel();
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python history:', error);
|
||
}
|
||
}
|
||
|
||
function updatePythonHistoryPanel() {
|
||
const panel = document.getElementById('python-history-list');
|
||
if (!panel) return;
|
||
|
||
if (pythonHistory.length === 0) {
|
||
panel.innerHTML = '<p class="text-gray-500 text-sm">No hay historial de ejecuciones</p>';
|
||
return;
|
||
}
|
||
|
||
panel.innerHTML = '';
|
||
pythonHistory.slice(0, 10).forEach(entry => {
|
||
const historyItem = document.createElement('div');
|
||
historyItem.className = 'flex items-center justify-between bg-gray-50 p-3 rounded border';
|
||
|
||
const timestamp = new Date(entry.timestamp).toLocaleString();
|
||
const statusIcon = entry.status === 'completed' ? '✅' :
|
||
entry.status === 'error' ? '❌' : '🔄';
|
||
const executionTime = entry.execution_time ? ` (${entry.execution_time.toFixed(2)}s)` : '';
|
||
|
||
historyItem.innerHTML = `
|
||
<div class="flex-1">
|
||
<div class="font-medium">
|
||
${statusIcon} ${entry.script_name}${executionTime}
|
||
</div>
|
||
<div class="text-sm text-gray-600">
|
||
${entry.arguments.length > 0 ? `Args: ${entry.arguments.join(' ')}` : 'Sin argumentos'}
|
||
</div>
|
||
<div class="text-xs text-gray-500">${timestamp}</div>
|
||
</div>
|
||
<div class="text-xs text-gray-400">
|
||
${entry.python_env || 'base'}
|
||
</div>
|
||
`;
|
||
panel.appendChild(historyItem);
|
||
});
|
||
}
|
||
|
||
async function clearPythonHistory() {
|
||
if (!confirm('¿Estás seguro de que quieres limpiar el historial?')) return;
|
||
|
||
try {
|
||
const response = await fetch('/api/python-history', {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.error) {
|
||
alert(`Error: ${result.error}`);
|
||
} else {
|
||
console.log('History cleared:', result.message);
|
||
loadPythonHistory();
|
||
}
|
||
} catch (error) {
|
||
console.error('Error clearing Python history:', error);
|
||
}
|
||
}
|
||
|
||
// === FUNCIONES DE INTEGRACIÓN CON EDITORES ===
|
||
|
||
function openPythonProjectInEditor(editor) {
|
||
if (!selectedPythonProject) {
|
||
alert('Selecciona un proyecto primero');
|
||
return;
|
||
}
|
||
|
||
openGroupInEditor(editor, 'python', selectedPythonProject.id);
|
||
}
|
||
|
||
function openPythonProjectFolder() {
|
||
if (!selectedPythonProject) {
|
||
alert('Selecciona un proyecto primero');
|
||
return;
|
||
}
|
||
|
||
openGroupFolder('python', selectedPythonProject.id);
|
||
}
|
||
|
||
async function copyPythonProjectPath() {
|
||
if (!selectedPythonProject) {
|
||
alert('Selecciona un proyecto primero');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await navigator.clipboard.writeText(selectedPythonProject.directory);
|
||
// Mostrar mensaje temporal de éxito
|
||
const button = document.getElementById('copy-path-python-btn');
|
||
const originalText = button.textContent;
|
||
button.textContent = '✓';
|
||
setTimeout(() => button.textContent = originalText, 1000);
|
||
} catch (error) {
|
||
console.error('Error copying path:', error);
|
||
alert('Error al copiar el path al portapapeles');
|
||
}
|
||
}
|
||
|
||
// === GESTIÓN DE ARCHIVOS MARKDOWN ===
|
||
|
||
function renderPythonMarkdownFiles() {
|
||
const container = document.getElementById('python-markdown-files-section');
|
||
if (!container) return;
|
||
|
||
if (!selectedPythonProject || pythonMarkdownFiles.length === 0) {
|
||
container.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
container.style.display = 'block';
|
||
const grid = document.getElementById('python-markdown-files-grid');
|
||
if (!grid) return;
|
||
|
||
grid.innerHTML = '';
|
||
pythonMarkdownFiles.forEach(file => {
|
||
const card = document.createElement('div');
|
||
card.className = 'markdown-file-card bg-white border rounded-lg p-3 hover:shadow-md transition-shadow cursor-pointer';
|
||
card.innerHTML = `
|
||
<div class="flex items-start justify-between">
|
||
<div class="flex-1">
|
||
<h4 class="font-medium text-gray-900 text-sm">${file.title || file.filename}</h4>
|
||
<p class="text-xs text-gray-500 mt-1">
|
||
📄 ${(file.size / 1024).toFixed(1)} KB
|
||
${file.relative_path.includes('/') ? ' • Subdirectorio' : ''}
|
||
</p>
|
||
</div>
|
||
<span class="text-xs text-gray-400">${getTimeAgo(file.modified)}</span>
|
||
</div>
|
||
`;
|
||
card.onclick = () => openPythonMarkdownViewer(file.relative_path, file.title || file.filename);
|
||
grid.appendChild(card);
|
||
});
|
||
}
|
||
|
||
async function openPythonMarkdownViewer(relativePath, displayName) {
|
||
if (!selectedPythonProject) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/python-markdown-content/${selectedPythonProject.id}/${encodeURIComponent(relativePath)}`);
|
||
const result = await response.json();
|
||
|
||
if (result.content) {
|
||
const modal = document.getElementById('python-markdown-viewer-modal');
|
||
const titleElement = document.getElementById('python-markdown-viewer-title');
|
||
const pathElement = document.getElementById('python-markdown-viewer-path');
|
||
const contentElement = document.getElementById('python-markdown-viewer-content');
|
||
|
||
if (modal && titleElement && contentElement) {
|
||
titleElement.textContent = displayName;
|
||
pathElement.textContent = `Archivo: ${relativePath}`;
|
||
|
||
// Renderizar markdown
|
||
const md = window.markdownit();
|
||
contentElement.innerHTML = md.render(result.content);
|
||
|
||
modal.classList.remove('hidden');
|
||
}
|
||
} else {
|
||
alert(`Error: ${result.error || 'Error cargando archivo'}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python markdown file:', error);
|
||
alert('Error cargando archivo Markdown');
|
||
}
|
||
}
|
||
|
||
function closePythonMarkdownViewer() {
|
||
const modal = document.getElementById('python-markdown-viewer-modal');
|
||
if (modal) {
|
||
modal.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
// === GESTIÓN DE DESCRIPCIONES DE SCRIPTS ===
|
||
|
||
async function showPythonScriptDescription(scriptName, displayName) {
|
||
if (!selectedPythonProject) return;
|
||
|
||
try {
|
||
// Cargar metadatos del script para obtener la descripción larga
|
||
const response = await fetch(`/api/python-script-metadata/${selectedPythonProject.id}/${scriptName}`);
|
||
const metadata = await response.json();
|
||
|
||
const modal = document.getElementById('python-script-description-modal');
|
||
const scriptNameElement = document.getElementById('python-desc-modal-script-name');
|
||
const scriptFileElement = document.getElementById('python-desc-modal-script-file');
|
||
const contentElement = document.getElementById('python-script-description-content');
|
||
|
||
if (modal && scriptNameElement && contentElement) {
|
||
scriptNameElement.textContent = displayName;
|
||
scriptFileElement.textContent = `Archivo: ${scriptName}`;
|
||
|
||
// Renderizar markdown
|
||
if (metadata.long_description && metadata.long_description.trim()) {
|
||
const md = window.markdownit();
|
||
contentElement.innerHTML = md.render(metadata.long_description);
|
||
} else {
|
||
contentElement.innerHTML = '<p class="text-gray-500 italic">No hay descripción disponible para este script.</p>';
|
||
}
|
||
|
||
modal.classList.remove('hidden');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading Python script description:', error);
|
||
alert('Error cargando la descripción del script');
|
||
}
|
||
}
|
||
|
||
function closePythonScriptDescription() {
|
||
const modal = document.getElementById('python-script-description-modal');
|
||
if (modal) {
|
||
modal.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
// === GESTIÓN DE PROYECTOS ===
|
||
|
||
function openPythonProjectEditor() {
|
||
const modal = document.getElementById('python-project-editor-modal');
|
||
if (modal) {
|
||
currentEditingPythonProject = null;
|
||
clearPythonProjectForm();
|
||
renderExistingPythonProjects();
|
||
modal.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
function closePythonProjectEditor() {
|
||
const modal = document.getElementById('python-project-editor-modal');
|
||
if (modal) {
|
||
modal.classList.add('hidden');
|
||
currentEditingPythonProject = null;
|
||
}
|
||
}
|
||
|
||
function clearPythonProjectForm() {
|
||
document.getElementById('python-project-name').value = '';
|
||
document.getElementById('python-project-description').value = '';
|
||
document.getElementById('python-project-category').value = 'MCP Servers';
|
||
document.getElementById('python-project-version').value = '1.0';
|
||
document.getElementById('python-project-python-env').value = 'base';
|
||
document.getElementById('python-project-directory').value = '';
|
||
document.getElementById('delete-python-project-btn').style.display = 'none';
|
||
}
|
||
|
||
function populatePythonProjectForm(project) {
|
||
document.getElementById('python-project-name').value = project.name;
|
||
document.getElementById('python-project-description').value = project.description || '';
|
||
document.getElementById('python-project-category').value = project.category;
|
||
document.getElementById('python-project-version').value = project.version || '1.0';
|
||
document.getElementById('python-project-python-env').value = project.python_env || 'base';
|
||
document.getElementById('python-project-directory').value = project.directory;
|
||
}
|
||
|
||
function renderExistingPythonProjects() {
|
||
const list = document.getElementById('existing-python-projects-list');
|
||
if (!list) return;
|
||
|
||
if (pythonProjects.length === 0) {
|
||
list.innerHTML = '<p class="text-gray-500 text-sm">No hay proyectos Python creados aún</p>';
|
||
return;
|
||
}
|
||
|
||
list.innerHTML = '';
|
||
pythonProjects.forEach(project => {
|
||
const item = document.createElement('div');
|
||
item.className = 'flex justify-between items-center p-2 border rounded hover:bg-gray-50 cursor-pointer';
|
||
item.innerHTML = `
|
||
<div class="flex-1">
|
||
<span class="font-medium">${project.name}</span>
|
||
<span class="text-sm text-gray-500 ml-2">(${project.category})</span>
|
||
</div>
|
||
<button onclick="editPythonProject('${project.id}')"
|
||
class="text-blue-500 hover:text-blue-700 text-sm">
|
||
Editar
|
||
</button>
|
||
`;
|
||
list.appendChild(item);
|
||
});
|
||
}
|
||
|
||
function editPythonProject(projectId) {
|
||
const project = pythonProjects.find(p => p.id === projectId);
|
||
if (!project) return;
|
||
|
||
currentEditingPythonProject = project;
|
||
populatePythonProjectForm(project);
|
||
document.getElementById('delete-python-project-btn').style.display = 'block';
|
||
}
|
||
|
||
async function savePythonProject() {
|
||
const formData = {
|
||
name: document.getElementById('python-project-name').value.trim(),
|
||
description: document.getElementById('python-project-description').value.trim(),
|
||
category: document.getElementById('python-project-category').value,
|
||
version: document.getElementById('python-project-version').value.trim(),
|
||
python_env: document.getElementById('python-project-python-env').value,
|
||
directory: document.getElementById('python-project-directory').value.trim()
|
||
};
|
||
|
||
if (!formData.name || !formData.directory) {
|
||
alert('Por favor, completa todos los campos obligatorios (Nombre, Directorio)');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const isEdit = currentEditingPythonProject !== null;
|
||
const url = isEdit ? `/api/python-projects/${currentEditingPythonProject.id}` : '/api/python-projects';
|
||
const method = isEdit ? 'PUT' : 'POST';
|
||
|
||
const response = await fetch(url, {
|
||
method: method,
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(formData)
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.status === 'success') {
|
||
await loadPythonProjects();
|
||
closePythonProjectEditor();
|
||
alert(isEdit ? 'Proyecto actualizado correctamente' : 'Proyecto creado correctamente');
|
||
} else {
|
||
alert(`Error: ${result.message}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error saving Python project:', error);
|
||
alert('Error al guardar el proyecto');
|
||
}
|
||
}
|
||
|
||
async function deletePythonProject() {
|
||
if (!currentEditingPythonProject) return;
|
||
|
||
if (!confirm(`¿Estás seguro de que quieres eliminar el proyecto "${currentEditingPythonProject.name}"?`)) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/python-projects/${currentEditingPythonProject.id}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.status === 'success') {
|
||
await loadPythonProjects();
|
||
closePythonProjectEditor();
|
||
|
||
// Limpiar selección si era el proyecto actual
|
||
if (selectedPythonProject && selectedPythonProject.id === currentEditingPythonProject.id) {
|
||
document.getElementById('python-project-select').value = '';
|
||
selectedPythonProject = null;
|
||
pythonScripts = [];
|
||
pythonMarkdownFiles = [];
|
||
updatePythonScriptsGrid();
|
||
updatePythonProjectButtons();
|
||
renderPythonMarkdownFiles();
|
||
localStorage.removeItem('python-selected-project');
|
||
}
|
||
|
||
alert('Proyecto eliminado correctamente');
|
||
} else {
|
||
alert(`Error: ${result.message}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error deleting Python project:', error);
|
||
alert('Error al eliminar el proyecto');
|
||
}
|
||
}
|
||
|
||
function browsePythonProjectDirectory() {
|
||
// Similar a la función existente pero para el formulario de proyectos Python
|
||
fetch('/api/browse-directories')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.status === 'success') {
|
||
document.getElementById('python-project-directory').value = data.path;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error browsing directory:', error);
|
||
});
|
||
}
|
||
|
||
// === GESTIÓN DE SCRIPTS INDIVIDUALES ===
|
||
|
||
function openPythonScriptManager() {
|
||
if (!selectedPythonProject) {
|
||
alert('Selecciona un proyecto primero');
|
||
return;
|
||
}
|
||
|
||
const modal = document.getElementById('python-script-manager-modal');
|
||
const projectInfo = document.getElementById('python-script-manager-project-info');
|
||
|
||
if (modal && projectInfo) {
|
||
projectInfo.textContent = `Proyecto: ${selectedPythonProject.name} (${selectedPythonProject.category})`;
|
||
loadAllPythonScriptsForManagement();
|
||
modal.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
function closePythonScriptManager() {
|
||
const modal = document.getElementById('python-script-manager-modal');
|
||
if (modal) {
|
||
modal.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
async function loadAllPythonScriptsForManagement() {
|
||
if (!selectedPythonProject) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/python-scripts-all/${selectedPythonProject.id}`);
|
||
const allScripts = await response.json();
|
||
renderPythonScriptManagerList(allScripts);
|
||
} catch (error) {
|
||
console.error('Error loading all Python scripts for management:', error);
|
||
}
|
||
}
|
||
|
||
function renderPythonScriptManagerList(scripts) {
|
||
const list = document.getElementById('python-script-manager-list');
|
||
if (!list) return;
|
||
|
||
if (scripts.length === 0) {
|
||
list.innerHTML = `
|
||
<div class="text-center text-gray-500 py-8">
|
||
<p>No hay scripts Python en este directorio</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
list.innerHTML = '';
|
||
scripts.forEach(script => {
|
||
const item = document.createElement('div');
|
||
item.className = `border rounded-lg p-4 ${script.hidden ? 'bg-gray-50 border-gray-300' : 'bg-white border-gray-200'}`;
|
||
item.innerHTML = `
|
||
<div class="flex justify-between items-start">
|
||
<div class="flex-1">
|
||
<div class="flex items-center gap-2 mb-1">
|
||
<h4 class="font-medium ${script.hidden ? 'text-gray-500' : 'text-gray-900'}">${script.display_name}</h4>
|
||
${script.hidden ? '<span class="text-xs bg-gray-200 text-gray-600 px-2 py-1 rounded">Oculto</span>' : ''}
|
||
</div>
|
||
<p class="text-sm text-gray-600 mb-2">${script.description || 'Sin descripción'}</p>
|
||
<p class="text-xs text-gray-500">Archivo: ${script.name}</p>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button onclick="editPythonScriptMetadata('${script.name}')"
|
||
class="text-blue-500 hover:underline text-sm">
|
||
Editar
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
list.appendChild(item);
|
||
});
|
||
}
|
||
|
||
async function editPythonScriptMetadata(scriptName) {
|
||
if (!selectedPythonProject) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/python-script-metadata/${selectedPythonProject.id}/${scriptName}`);
|
||
const metadata = await response.json();
|
||
|
||
// Poblar el formulario
|
||
document.getElementById('edit-python-meta-project-id').value = selectedPythonProject.id;
|
||
document.getElementById('edit-python-meta-script-name').value = scriptName;
|
||
document.getElementById('edit-python-meta-filename-display').textContent = scriptName;
|
||
document.getElementById('edit-python-meta-display-name').value = metadata.display_name || scriptName.replace('.py', '');
|
||
document.getElementById('edit-python-meta-description').value = metadata.description || '';
|
||
document.getElementById('edit-python-meta-long-description').value = metadata.long_description || '';
|
||
document.getElementById('edit-python-meta-hidden').checked = metadata.hidden || false;
|
||
|
||
// Mostrar modal
|
||
document.getElementById('python-script-metadata-editor-modal').classList.remove('hidden');
|
||
} catch (error) {
|
||
console.error('Error loading Python script metadata:', error);
|
||
alert('Error cargando metadatos del script');
|
||
}
|
||
}
|
||
|
||
function closePythonScriptMetadataEditor() {
|
||
const modal = document.getElementById('python-script-metadata-editor-modal');
|
||
if (modal) {
|
||
modal.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
async function savePythonScriptMetadata() {
|
||
const formData = {
|
||
display_name: document.getElementById('edit-python-meta-display-name').value.trim(),
|
||
description: document.getElementById('edit-python-meta-description').value.trim(),
|
||
long_description: document.getElementById('edit-python-meta-long-description').value.trim(),
|
||
hidden: document.getElementById('edit-python-meta-hidden').checked
|
||
};
|
||
|
||
const projectId = document.getElementById('edit-python-meta-project-id').value;
|
||
const scriptName = document.getElementById('edit-python-meta-script-name').value;
|
||
|
||
if (!formData.display_name) {
|
||
alert('El nombre a mostrar es obligatorio');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/api/python-script-metadata/${projectId}/${scriptName}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(formData)
|
||
});
|
||
|
||
const result = await response.json();
|
||
if (result.status === 'success') {
|
||
await loadAllPythonScriptsForManagement();
|
||
await loadPythonScripts(); // Recargar scripts visibles también
|
||
closePythonScriptMetadataEditor();
|
||
alert('Metadatos guardados correctamente');
|
||
} else {
|
||
alert(`Error: ${result.message}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error saving Python script metadata:', error);
|
||
alert('Error al guardar metadatos');
|
||
}
|
||
}
|
||
|
||
// === UTILIDADES ===
|
||
|
||
function getTimeAgo(dateString) {
|
||
const now = new Date();
|
||
const date = new Date(dateString);
|
||
const diffMs = now - date;
|
||
|
||
const diffMins = Math.floor(diffMs / (1000 * 60));
|
||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||
|
||
if (diffMins < 60) {
|
||
return `${diffMins}min`;
|
||
} else if (diffHours < 24) {
|
||
return `${diffHours}h`;
|
||
} else {
|
||
return `${diffDays}d`;
|
||
}
|
||
}
|
||
|
||
// === FUNCIONES GLOBALES REQUERIDAS ===
|
||
|
||
// Estas funciones son llamadas desde el HTML pero están implementadas en scripts.js
|
||
// Solo las declaramos aquí para evitar errores
|
||
|
||
if (typeof openGroupInEditor === 'undefined') {
|
||
window.openGroupInEditor = function (editor, groupSystem, groupId) {
|
||
console.log(`Opening ${editor} for ${groupSystem} group ${groupId}`);
|
||
// Implementación en scripts.js
|
||
};
|
||
}
|
||
|
||
if (typeof openGroupFolder === 'undefined') {
|
||
window.openGroupFolder = function (groupSystem, groupId) {
|
||
console.log(`Opening folder for ${groupSystem} group ${groupId}`);
|
||
// Implementación en scripts.js
|
||
};
|
||
}
|
||
|
||
console.log('Python Launcher JavaScript loaded'); |