ParamManagerScripts/static/js/python_launcher.js

1251 lines
45 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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');