// MCP Server Frontend JavaScript class MCPServerFrontend { constructor() { this.currentTaskId = null; this.statusInterval = null; this.init(); } init() { this.bindEvents(); this.setDefaultValues(); } bindEvents() { // Process button document.getElementById('processBtn').addEventListener('click', () => { this.processRepository(); }); // Clear log button document.getElementById('clearLog').addEventListener('click', () => { this.clearLog(); }); // Toggle API key visibility document.getElementById('toggleApiKey').addEventListener('click', () => { this.toggleApiKeyVisibility(); }); // Toggle Gitea token visibility document.getElementById('toggleGiteaToken').addEventListener('click', () => { this.toggleGiteaTokenVisibility(); }); // Form validation document.getElementById('repoUrl').addEventListener('input', () => { this.validateForm(); }); document.getElementById('giteaToken').addEventListener('input', () => { this.validateForm(); }); document.getElementById('prompt').addEventListener('input', () => { this.validateForm(); }); document.getElementById('apiKey').addEventListener('input', () => { this.validateForm(); }); document.getElementById('modelName').addEventListener('input', () => { this.validateForm(); }); } setDefaultValues() { // Set default values for testing document.getElementById('repoUrl').value = 'http://157.66.191.31:3000/user/repo.git'; document.getElementById('giteaToken').value = '37c322628fa57b0ec7b481c8655ae2bebd486f6f'; document.getElementById('aiModel').value = 'gemini'; } toggleApiKeyVisibility() { const apiKeyInput = document.getElementById('apiKey'); const toggleBtn = document.getElementById('toggleApiKey'); const icon = toggleBtn.querySelector('i'); if (apiKeyInput.type === 'password') { apiKeyInput.type = 'text'; icon.className = 'fas fa-eye-slash'; } else { apiKeyInput.type = 'password'; icon.className = 'fas fa-eye'; } } toggleGiteaTokenVisibility() { const giteaTokenInput = document.getElementById('giteaToken'); const toggleBtn = document.getElementById('toggleGiteaToken'); const icon = toggleBtn.querySelector('i'); if (giteaTokenInput.type === 'password') { giteaTokenInput.type = 'text'; icon.className = 'fas fa-eye-slash'; } else { giteaTokenInput.type = 'password'; icon.className = 'fas fa-eye'; } } validateForm() { const repoUrl = document.getElementById('repoUrl').value.trim(); const giteaToken = document.getElementById('giteaToken').value.trim(); const prompt = document.getElementById('prompt').value.trim(); const apiKey = document.getElementById('apiKey').value.trim(); const modelName = document.getElementById('modelName').value.trim(); const isValid = repoUrl && giteaToken && prompt && apiKey && modelName; const processBtn = document.getElementById('processBtn'); processBtn.disabled = !isValid; if (isValid) { processBtn.classList.remove('btn-secondary'); processBtn.classList.add('btn-primary'); } else { processBtn.classList.remove('btn-primary'); processBtn.classList.add('btn-secondary'); } return isValid; } async processRepository() { if (!this.validateForm()) { this.addLogEntry('error', 'Please fill in all required fields'); return; } const requestData = { repo_url: document.getElementById('repoUrl').value.trim(), token: document.getElementById('giteaToken').value.trim(), prompt: document.getElementById('prompt').value.trim(), ai_model: document.getElementById('aiModel').value, model_name: document.getElementById('modelName').value.trim(), api_key: document.getElementById('apiKey').value.trim() }; try { // Disable form this.setFormEnabled(false); this.showProgressCard(); this.updateStatus('Processing...', 'processing'); this.addLogEntry('info', 'Starting repository processing...'); // Show loading modal const loadingModal = new bootstrap.Modal(document.getElementById('loadingModal')); loadingModal.show(); // Send request to backend const response = await fetch('/process', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestData) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); this.currentTaskId = result.task_id; this.addLogEntry('success', `Task started with ID: ${result.task_id}`); this.addLogEntry('info', 'Monitoring task progress...'); // Start monitoring this.startStatusMonitoring(); // Hide loading modal loadingModal.hide(); } catch (error) { this.addLogEntry('error', `Failed to start processing: ${error.message}`); this.updateStatus('Error', 'error'); this.setFormEnabled(true); this.hideProgressCard(); // Hide loading modal const loadingModal = bootstrap.Modal.getInstance(document.getElementById('loadingModal')); if (loadingModal) { loadingModal.hide(); } } } startStatusMonitoring() { if (this.statusInterval) { clearInterval(this.statusInterval); } this.statusInterval = setInterval(async () => { if (!this.currentTaskId) { this.stopStatusMonitoring(); return; } try { const response = await fetch(`/status/${this.currentTaskId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const status = await response.json(); this.updateProgress(status); this.addLogEntry('info', status.message); if (status.status === 'completed' || status.status === 'error') { this.stopStatusMonitoring(); this.handleTaskCompletion(status); } } catch (error) { this.addLogEntry('error', `Failed to get status: ${error.message}`); this.stopStatusMonitoring(); } }, 2000); // Check every 2 seconds } stopStatusMonitoring() { if (this.statusInterval) { clearInterval(this.statusInterval); this.statusInterval = null; } } updateProgress(status) { const progressBar = document.getElementById('progressBar'); const steps = document.querySelectorAll('.step'); // Show AI output if available if (status.ai_response) { document.getElementById('aiOutputCard').style.display = 'block'; document.getElementById('aiOutput').textContent = status.ai_response; } else { document.getElementById('aiOutputCard').style.display = 'none'; document.getElementById('aiOutput').textContent = ''; } // Step order (no deps) const stepOrder = ['clone', 'ai', 'commit']; let currentStep = 0; let errorStep = -1; // Determine current step and error if (status.message.includes('Cloning')) { currentStep = 0; } else if (status.message.includes('Analyzing') || status.message.includes('AI')) { currentStep = 1; } else if (status.message.includes('Committing') || status.message.includes('Push')) { currentStep = 2; } else if (status.status === 'completed') { currentStep = 3; } else if (status.status === 'error') { // Try to find which step failed if (status.message.includes('Clone')) errorStep = 0; else if (status.message.includes('AI') || status.message.includes('Gemini') || status.message.includes('OpenAI')) errorStep = 1; else if (status.message.includes('Commit') || status.message.includes('Push')) errorStep = 2; } // Update progress bar let progress = (currentStep / stepOrder.length) * 100; progressBar.style.width = `${progress}%`; // Update step indicators with color, icon, and label steps.forEach((step, idx) => { const stepName = step.dataset.step; step.classList.remove('active', 'completed', 'error', 'pending'); const iconSpan = step.querySelector('.step-icon'); const labelSpan = step.querySelector('.step-label'); // Set icon and label if (errorStep === idx) { step.classList.add('error'); iconSpan.innerHTML = '✖'; labelSpan.textContent = 'Error'; } else if (idx < currentStep) { step.classList.add('completed'); iconSpan.innerHTML = '✔'; labelSpan.textContent = 'Completed'; } else if (idx === currentStep && status.status !== 'completed' && status.status !== 'error') { step.classList.add('active'); iconSpan.innerHTML = ''; labelSpan.textContent = 'In Progress'; } else { step.classList.add('pending'); iconSpan.innerHTML = '●'; labelSpan.textContent = 'Pending'; } }); } isStepCompleted(stepName, activeStep) { const stepOrder = ['clone', 'deps', 'ai', 'commit']; const stepIndex = stepOrder.indexOf(stepName); const activeIndex = stepOrder.indexOf(activeStep); return stepIndex < activeIndex; } handleTaskCompletion(status) { if (status.status === 'completed') { this.updateStatus('Completed', 'completed'); this.addLogEntry('success', 'Repository processing completed successfully!'); this.addLogEntry('info', 'Changes have been committed and pushed to the repository.'); } else { this.updateStatus('Error', 'error'); this.addLogEntry('error', `Processing failed: ${status.message}`); } this.setFormEnabled(true); this.currentTaskId = null; } updateStatus(text, type) { const statusText = document.querySelector('.status-text'); const statusDot = document.querySelector('.status-dot'); statusText.textContent = text; // Update status dot color statusDot.className = 'status-dot'; switch (type) { case 'processing': statusDot.style.backgroundColor = '#ffc107'; break; case 'completed': statusDot.style.backgroundColor = '#28a745'; break; case 'error': statusDot.style.backgroundColor = '#dc3545'; break; default: statusDot.style.backgroundColor = '#17a2b8'; } } addLogEntry(type, message) { const logContainer = document.getElementById('logContainer'); const timestamp = new Date().toLocaleTimeString(); const logEntry = document.createElement('div'); logEntry.className = `log-entry ${type}`; logEntry.innerHTML = ` `; logContainer.appendChild(logEntry); logContainer.scrollTop = logContainer.scrollHeight; } clearLog() { const logContainer = document.getElementById('logContainer'); logContainer.innerHTML = `