415 lines
14 KiB
JavaScript
415 lines
14 KiB
JavaScript
// 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 = '<span class="spinner-grow spinner-grow-sm" style="width:1em;height:1em;"></span>';
|
|
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 = `
|
|
<span class="timestamp">[${timestamp}]</span>
|
|
<span class="message">${message}</span>
|
|
`;
|
|
|
|
logContainer.appendChild(logEntry);
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
}
|
|
|
|
clearLog() {
|
|
const logContainer = document.getElementById('logContainer');
|
|
logContainer.innerHTML = `
|
|
<div class="log-entry info">
|
|
<span class="timestamp">[System]</span>
|
|
<span class="message">Log cleared. Ready for new operations.</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
setFormEnabled(enabled) {
|
|
const formElements = [
|
|
'repoUrl', 'giteaToken', 'prompt', 'aiModel', 'modelName', 'apiKey', 'processBtn'
|
|
];
|
|
|
|
formElements.forEach(id => {
|
|
const element = document.getElementById(id);
|
|
if (element) {
|
|
element.disabled = !enabled;
|
|
}
|
|
});
|
|
|
|
const processBtn = document.getElementById('processBtn');
|
|
if (enabled) {
|
|
processBtn.innerHTML = '<i class="fas fa-play"></i> Process Repository';
|
|
} else {
|
|
processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
|
|
}
|
|
}
|
|
|
|
showProgressCard() {
|
|
const progressCard = document.getElementById('progressCard');
|
|
progressCard.style.display = 'block';
|
|
|
|
// Reset progress
|
|
document.getElementById('progressBar').style.width = '0%';
|
|
document.querySelectorAll('.step').forEach(step => {
|
|
step.classList.remove('active', 'completed');
|
|
});
|
|
}
|
|
|
|
hideProgressCard() {
|
|
const progressCard = document.getElementById('progressCard');
|
|
progressCard.style.display = 'none';
|
|
}
|
|
|
|
// Utility function to format error messages
|
|
formatError(error) {
|
|
if (error.response) {
|
|
return `Server error: ${error.response.status} - ${error.response.statusText}`;
|
|
} else if (error.request) {
|
|
return 'Network error: Unable to connect to server';
|
|
} else {
|
|
return `Error: ${error.message}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize the frontend when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.mcpFrontend = new MCPServerFrontend();
|
|
});
|
|
|
|
// Handle page unload to clean up intervals
|
|
window.addEventListener('beforeunload', () => {
|
|
if (window.mcpFrontend) {
|
|
window.mcpFrontend.stopStatusMonitoring();
|
|
}
|
|
});
|