giteamcp/static/script.js
2025-07-15 14:32:59 +05:30

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();
}
});