codefix-ai / script.js
eubottura's picture
nao tá dando pra gerar relatorio estruturado
75bf439 verified
// Global state
const state = {
files: [],
fileContents: new Map(),
selectedFiles: new Set(),
processedText: '',
qwenResponse: '',
zaiResponse: '',
correctedFiles: [],
filesNeedingFix: [],
isProcessing: false,
currentTab: 'upload',
fileTreeStructure: null
};
// DOM Elements
const elements = {
// Upload tab
dropZone: document.getElementById('drop-zone'),
fileInput: document.getElementById('file-input'),
browseBtn: document.getElementById('browse-btn'),
fileTreeContainer: document.getElementById('file-tree-container'),
fileTree: document.getElementById('file-tree'),
generateReport: document.getElementById('generate-report'),
reportContainer: document.getElementById('report-container'),
reportOutput: document.getElementById('report-output'),
copyReport: document.getElementById('copy-report'),
downloadReport: document.getElementById('download-report'),
selectAll: document.getElementById('select-all'),
deselectAll: document.getElementById('deselect-all'),
// Qwen tab
problemInputQwen: document.getElementById('problem-input-qwen'),
voiceBtnQwen: document.getElementById('voice-btn-qwen'),
qwenProjectInput: document.getElementById('qwen-project-input'),
openQwen: document.getElementById('open-qwen'),
injectQwen: document.getElementById('inject-qwen'),
qwenStatus: document.getElementById('qwen-status'),
qwenProgress: document.getElementById('qwen-progress'),
qwenAnalysis: document.getElementById('qwen-analysis'),
sendToZai: document.getElementById('send-to-zai'),
// Zai tab
zaiAnalysisInput: document.getElementById('zai-analysis-input'),
openZai: document.getElementById('open-zai'),
injectZai: document.getElementById('inject-zai'),
zaiStatus: document.getElementById('zai-status'),
zaiProgress: document.getElementById('zai-progress'),
zaiCorrections: document.getElementById('zai-corrections'),
processCorrections: document.getElementById('process-corrections'),
// Results tab
resultsSummary: document.getElementById('results-summary'),
summaryText: document.getElementById('summary-text'),
correctedFilesList: document.getElementById('corrected-files-list'),
downloadAllCorrected: document.getElementById('download-all-corrected'),
generatePatch: document.getElementById('generate-patch'),
startNew: document.getElementById('start-new'),
// Notifications
errorContainer: document.getElementById('error-container'),
errorMessage: document.getElementById('error-message'),
dismissError: document.getElementById('dismiss-error'),
successContainer: document.getElementById('success-container'),
successMessage: document.getElementById('success-message'),
dismissSuccess: document.getElementById('dismiss-success'),
// Modals
bookmarkletModal: document.getElementById('bookmarklet-modal'),
closeBookmarklet: document.getElementById('close-bookmarklet'),
};
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
setupEventListeners();
setupTabNavigation();
});
// Setup tab navigation
function setupTabNavigation() {
const tabButtons = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
tabButtons.forEach(btn => {
btn.addEventListener('click', () => {
const targetTab = btn.dataset.tab;
// Update button states
tabButtons.forEach(b => {
b.classList.remove('active', 'border-primary', 'text-primary');
b.classList.add('border-transparent');
});
btn.classList.add('active', 'border-primary', 'text-primary');
btn.classList.remove('border-transparent');
// Update content visibility
tabContents.forEach(content => {
content.classList.add('hidden');
});
document.getElementById(`${targetTab}-tab`).classList.remove('hidden');
state.currentTab = targetTab;
});
});
}
// Set up event listeners
function setupEventListeners() {
// File upload
elements.browseBtn.addEventListener('click', () => elements.fileInput.click());
elements.dropZone.addEventListener('dragover', handleDragOver);
elements.dropZone.addEventListener('drop', handleDrop);
elements.fileInput.addEventListener('change', handleFileSelect);
// File tree selection
elements.selectAll.addEventListener('click', selectAllFiles);
elements.deselectAll.addEventListener('click', deselectAllFiles);
// Report generation
elements.generateReport.addEventListener('click', generateStructuredReport);
elements.copyReport.addEventListener('click', copyReportToClipboard);
elements.downloadReport.addEventListener('click', downloadReportFile);
// Voice input for Qwen
elements.voiceBtnQwen.addEventListener('click', () => startVoiceInput(elements.problemInputQwen));
// Qwen interactions
elements.openQwen.addEventListener('click', openQwenInHuggingFace);
elements.injectQwen.addEventListener('click', showBookmarkletInstructions);
elements.sendToZai.addEventListener('click', sendAnalysisToZai);
// Zai interactions
elements.openZai.addEventListener('click', openZaiInHuggingFace);
elements.injectZai.addEventListener('click', showBookmarkletInstructions);
elements.processCorrections.addEventListener('click', processZaiCorrections);
// Results actions
elements.downloadAllCorrected.addEventListener('click', downloadAllCorrectedFiles);
elements.generatePatch.addEventListener('click', generateGitPatch);
elements.startNew.addEventListener('click', resetApplication);
// Notifications
elements.dismissError.addEventListener('click', () => elements.errorContainer.classList.add('hidden'));
elements.dismissSuccess.addEventListener('click', () => elements.successContainer.classList.add('hidden'));
elements.closeBookmarklet.addEventListener('click', () => elements.bookmarkletModal.classList.add('hidden'));
}
// File handling functions
function handleDragOver(e) {
e.preventDefault();
e.stopPropagation();
elements.dropZone.classList.add('border-primary');
}
function handleDrop(e) {
e.preventDefault();
e.stopPropagation();
elements.dropZone.classList.remove('border-primary');
const items = e.dataTransfer.items;
if (items.length > 0) {
processDirectoryItems(items);
}
}
function handleFileSelect(e) {
const files = e.target.files;
if (files.length > 0) {
processDirectoryFiles(files);
}
}
// Process directory items from drag and drop
function processDirectoryItems(items) {
state.files = [];
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry();
if (item) {
traverseFileTree(item);
}
}
// Process files automatically after a short delay
setTimeout(() => {
processAllFiles();
}, 500);
}
// Process directory files from file input
function processDirectoryFiles(files) {
state.files = Array.from(files).map(file => ({
name: file.webkitRelativePath || file.name,
file: file,
isDirectory: false,
selected: true
}));
// Clear previous selected files
state.selectedFiles.clear();
// Add all files to selected set
state.files.forEach(fileObj => {
state.selectedFiles.add(fileObj.name);
});
// Build file tree structure
buildFileTreeStructure();
// Render file tree
renderFileTree();
// Show file tree container
elements.fileTreeContainer.classList.remove('hidden');
// Enable generate button
elements.generateReport.disabled = false;
showNotification(`${files.length} arquivos carregados com sucesso!`, 'success');
}
// Build hierarchical file tree structure
function buildFileTreeStructure() {
const tree = {};
state.files.forEach(fileObj => {
const path = fileObj.name;
const parts = path.split('/');
let current = tree;
parts.forEach((part, index) => {
if (index === parts.length - 1) {
// This is a file
current[part] = {
type: 'file',
path: path,
file: fileObj.file,
selected: fileObj.selected
};
} else {
// This is a directory
if (!current[part]) {
current[part] = {
type: 'directory',
children: {}
};
}
current = current[part].children;
}
});
});
state.fileTreeStructure = tree;
}
// Render file tree in the DOM
function renderFileTree() {
elements.fileTree.innerHTML = '';
renderTreeNodes(state.fileTreeStructure, elements.fileTree, '');
}
// Render tree nodes recursively
function renderTreeNodes(tree, container, parentPath) {
Object.keys(tree).sort().forEach(key => {
const node = tree[key];
const fullPath = parentPath ? `${parentPath}/${key}` : key;
const nodeElement = document.createElement('div');
nodeElement.className = 'tree-node';
if (node.type === 'directory') {
nodeElement.innerHTML = `
<div class="tree-item tree-folder">
<input type="checkbox" class="folder-checkbox" data-path="${fullPath}" checked>
<i data-feather="folder" class="w-4 h-4 text-amber-600 mr-2"></i>
<span class="folder-name">${key}</span>
</div>
<div class="tree-children ml-4 border-l border-gray-200 pl-2"></div>
`;
container.appendChild(nodeElement);
const childrenContainer = nodeElement.querySelector('.tree-children');
renderTreeNodes(node.children, childrenContainer, fullPath);
} else {
nodeElement.innerHTML = `
<div class="tree-item tree-file">
<input type="checkbox" class="file-checkbox" data-path="${node.path}" checked>
<i data-feather="file" class="w-4 h-4 text-blue-600 mr-2"></i>
<span class="file-name">${key}</span>
</div>
`;
container.appendChild(nodeElement);
// Store file reference
state.selectedFiles.add(node.path);
}
// Add event listener for checkbox
const checkbox = nodeElement.querySelector('input[type="checkbox"]');
checkbox.addEventListener('change', (e) => {
handleFileSelection(e.target, node, fullPath);
});
});
feather.replace();
}
// Handle file selection changes
function handleFileSelection(checkbox, node, path) {
if (node.type === 'directory') {
// Toggle all children
const childCheckboxes = checkbox.closest('.tree-node').querySelectorAll('.file-checkbox, .folder-checkbox');
childCheckboxes.forEach(cb => {
cb.checked = checkbox.checked;
const childPath = cb.dataset.path;
if (checkbox.checked) {
state.selectedFiles.add(childPath);
} else {
state.selectedFiles.delete(childPath);
}
});
} else {
// Toggle single file
if (checkbox.checked) {
state.selectedFiles.add(path);
} else {
state.selectedFiles.delete(path);
}
}
// Enable/disable generate button based on selection
const totalFiles = document.querySelectorAll('.file-checkbox').length;
const selectedCount = document.querySelectorAll('.file-checkbox:checked').length;
elements.generateReport.disabled = selectedCount === 0 && totalFiles > 0;
// Update selection info
console.log(`Arquivos selecionados: ${state.selectedFiles.size} de ${totalFiles}`);
}
// Select all files
function selectAllFiles() {
document.querySelectorAll('.tree-node input[type="checkbox"]').forEach(checkbox => {
checkbox.checked = true;
});
state.files.forEach(fileObj => {
state.selectedFiles.add(fileObj.name);
});
elements.generateReport.disabled = false;
}
// Deselect all files
function deselectAllFiles() {
document.querySelectorAll('.tree-node input[type="checkbox"]').forEach(checkbox => {
checkbox.checked = false;
});
state.selectedFiles.clear();
elements.generateReport.disabled = true;
}
// Traverse file tree (for drag and drop)
function traverseFileTree(item, path = '') {
if (item.isFile) {
item.file(file => {
state.files.push({
name: path + file.name,
file: file,
isDirectory: false,
selected: true
});
});
} else if (item.isDirectory) {
// Get folder contents
const dirReader = item.createReader();
dirReader.readEntries(entries => {
for (let i = 0; i < entries.length; i++) {
traverseFileTree(entries[i], path + item.name + "/");
}
});
}
}
// Generate structured report
async function generateStructuredReport() {
// Check if we have files uploaded
if (state.files.length === 0) {
showError('Por favor, faça upload dos arquivos do projeto primeiro.');
return;
}
// Get selected files from checkboxes or use all if none selected
let selectedFilesArray = [];
const checkedBoxes = document.querySelectorAll('.tree-node input[type="checkbox"]:checked');
if (checkedBoxes.length === 0) {
// If no files are selected, use all uploaded files
selectedFilesArray = state.files.map(f => f.name);
} else {
// Use checked files
checkedBoxes.forEach(checkbox => {
const path = checkbox.dataset.path;
if (path) {
selectedFilesArray.push(path);
}
});
}
if (selectedFilesArray.length === 0) {
showError('Por favor, selecione pelo menos um arquivo para gerar o relatório.');
return;
}
try {
elements.generateReport.disabled = true;
elements.generateReport.innerHTML = '<i data-feather="loader" class="w-5 h-5 inline mr-2 animate-spin"></i> Gerando...';
// Process selected files
const fileContents = [];
for (const filePath of selectedFilesArray) {
const fileObj = state.files.find(f => f.name === filePath);
if (fileObj) {
try {
const content = await readFileAsText(fileObj.file);
fileContents.push({
path: filePath,
content: content
});
// Store file content in state
state.fileContents.set(filePath, content);
} catch (e) {
console.warn(`Não foi possível ler o arquivo ${filePath}:`, e);
}
}
}
if (fileContents.length === 0) {
showError('Não foi possível ler nenhum arquivo. Verifique se os arquivos são de texto.');
return;
}
// Generate structured text output for AI analysis
let output = '=== PROJETO ESTRUTURADO PARA ANÁLISE ===\n\n';
output += `Total de arquivos: ${fileContents.length}\n`;
output += `Data de geração: ${new Date().toLocaleString('pt-BR')}\n\n`;
output += '--- CONTEÚDO DOS ARQUIVOS ---\n\n';
for (const fileContent of fileContents) {
output += `Arquivo: ${fileContent.path}\n`;
output += `"${fileContent.content}"\n\n`;
}
state.processedText = output;
elements.reportOutput.value = output;
elements.reportContainer.classList.remove('hidden');
showNotification(`Relatório gerado com ${fileContents.length} arquivos!`, 'success');
} catch (error) {
console.error('Erro detalhado:', error);
showError('Erro ao gerar relatório: ' + error.message);
} finally {
elements.generateReport.disabled = false;
elements.generateReport.innerHTML = '<i data-feather="file-text" class="w-5 h-5 inline mr-2"></i> Gerar Relatório Estruturado';
}
}
// Copy report to clipboard
function copyReportToClipboard() {
elements.reportOutput.select();
document.execCommand('copy');
showNotification('Relatório copiado para a área de transferência!', 'success');
}
// Download report as file
function downloadReportFile() {
const blob = new Blob([state.processedText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'project-report.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification('Relatório baixado com sucesso!', 'success');
}
// Read file as text
function readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsText(file, 'UTF-8');
});
}
// Voice input
function startVoiceInput(inputElement) {
if (!('webkitSpeechRecognition' in window)) {
showError('Reconhecimento de voz não suportado neste navegador.');
return;
}
const recognition = new webkitSpeechRecognition();
recognition.lang = 'pt-BR';
recognition.interimResults = false;
recognition.maxAlternatives = 1;
recognition.start();
showNotification('Ouvindo... Fale agora.', 'info');
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
inputElement.value = transcript;
showNotification('Texto reconhecido com sucesso!', 'success');
};
recognition.onerror = (event) => {
showError('Erro no reconhecimento de voz: ' + event.error);
};
}
// Open Qwen in Hugging Face
function openQwenInHuggingFace() {
window.open('https://huggingface.co/Qwen/Qwen3-235B-A22B-Instruct-2507', '_blank');
showNotification('Abrindo Qwen no Hugging Face...', 'info');
}
// Open Zai in Hugging Face
function openZaiInHuggingFace() {
window.open('https://huggingface.co/zai-org/GLM-4.6', '_blank');
showNotification('Abrindo Zai no Hugging Face...', 'info');
}
// Show bookmarklet instructions
function showBookmarkletInstructions() {
elements.bookmarkletModal.classList.remove('hidden');
}
// Send analysis to Zai tab
function sendAnalysisToZai() {
const analysis = elements.qwenAnalysis.textContent;
if (!analysis) {
showError('Nenhuma análise disponível para enviar.');
return;
}
// Switch to Zai tab
document.querySelector('[data-tab="zai"]').click();
// Fill Zai input with analysis
elements.zaiAnalysisInput.value = analysis;
showNotification('Análise enviada para o Zai!', 'success');
}
// Process Zai corrections
function processZaiCorrections() {
const corrections = elements.zaiCorrections.textContent;
if (!corrections) {
showError('Nenhuma correção disponível para processar.');
return;
}
try {
// Parse corrected files from Zai response
const correctedFiles = parseZAIFiles(corrections);
if (correctedFiles.length === 0) {
showError('Nenhum arquivo corrigido encontrado na resposta do Zai.');
return;
}
state.correctedFiles = correctedFiles;
// Switch to results tab
document.querySelector('[data-tab="results"]').click();
// Render results
renderFinalResults(correctedFiles);
showNotification(`${correctedFiles.length} arquivo(s) corrigido(s) processado(s) com sucesso!`, 'success');
} catch (error) {
showError('Erro ao processar correções: ' + error.message);
}
}
// Render final results
function renderFinalResults(correctedFiles) {
elements.resultsSummary.classList.remove('hidden');
elements.summaryText.textContent = `${correctedFiles.length} arquivo(s) foram corrigidos com sucesso pela IA colaborativa Qwen + Zai.`;
elements.correctedFilesList.innerHTML = '';
correctedFiles.forEach((file, index) => {
const fileElement = document.createElement('div');
fileElement.className = 'flex justify-between items-center bg-gray-50 p-4 rounded-lg border border-gray-200';
fileElement.innerHTML = `
<div class="flex-1">
<div class="flex items-center">
<i data-feather="file-text" class="w-5 h-5 text-green-600 mr-3"></i>
<span class="font-medium text-gray-800">${file.path}</span>
<span class="ml-2 px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">Corrigido</span>
</div>
<p class="text-sm text-gray-600 mt-1">${file.content.length} caracteres</p>
</div>
<button class="download-corrected-file bg-primary text-white px-4 py-2 rounded-lg hover:bg-indigo-600 transition-colors" data-index="${index}">
<i data-feather="download" class="w-4 h-4"></i>
</button>
`;
elements.correctedFilesList.appendChild(fileElement);
});
// Add event listeners to download buttons
document.querySelectorAll('.download-corrected-file').forEach(btn => {
btn.addEventListener('click', (e) => {
const index = parseInt(e.currentTarget.dataset.index);
const file = correctedFiles[index];
downloadCorrectedFile(file);
});
});
feather.replace();
}
// Real AI Analysis using OpenAI API (or similar)
async function simulateQwenAnalysis(problem, projectText) {
updateProgress('qwen', 10, 'Connecting to AI service...');
try {
// Using a public AI API for demonstration
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY' // Replace with actual API key
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [
{
role: 'system',
content: 'You are an expert code analyzer. Analyze the provided code and identify issues related to the user\'s problem. List specific files and line numbers with issues.'
},
{
role: 'user',
content: `Problem: ${problem}\n\nProject Code:\n${projectText.substring(0, 15000)}... (truncated for API limit)`
}
],
max_tokens: 1000,
temperature: 0.1
})
});
if (!response.ok) {
throw new Error('AI service unavailable');
}
const data = await response.json();
const analysisResult = data.choices[0].message.content;
updateProgress('qwen', 80, 'Analyzing findings...');
// Parse the analysis to extract files that need fixing
const filesNeedingFix = extractFilesFromAnalysis(analysisResult);
state.filesNeedingFix = filesNeedingFix;
updateProgress('qwen', 100, 'Analysis complete');
return `
🔍 QWEN AI ANALYSIS COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 PROBLEM IDENTIFIED:
${problem}
📁 FILES REQUIRING ATTENTION:
${filesNeedingFix.map(f => `• ${f.path}: ${f.issue}`).join('\n')}
📝 DETAILED ANALYSIS:
${analysisResult}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`;
} catch (error) {
console.error('AI Analysis failed:', error);
// Fallback to simulated analysis if API fails
updateProgress('qwen', 50, 'Using offline analysis...');
const files = Array.from(state.fileContents.keys());
const issues = analyzeProjectStructure(files, state.fileContents, problem);
state.filesNeedingFix = issues;
updateProgress('qwen', 100, 'Offline analysis complete');
return `
🔍 QWEN AI ANALYSIS COMPLETE (Offline Mode)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 PROBLEM IDENTIFIED:
${problem}
📁 FILES REQUIRING ATTENTION:
${issues.map(f => `• ${f.path}: ${f.issue}`).join('\n')}
📝 ANALYSIS SUMMARY:
Found ${issues.length} potential issues in the codebase that match the reported problem.
`;
}
}
// Real AI Correction using uploaded files
async function simulateZAICorrection(analysisResult) {
updateProgress('zai', 10, 'Preparing corrections...');
try {
const correctedFiles = [];
// Process each file that needs fixing
for (const fileData of state.filesNeedingFix) {
updateProgress('zai', 30 + (correctedFiles.length * 20), `Fixing ${fileData.path}...`);
const originalContent = state.fileContents.get(fileData.path);
if (!originalContent) continue;
// Use AI to fix the specific file
const correctedContent = await fixFileWithAI(fileData, originalContent, analysisResult);
correctedFiles.push({
path: fileData.path,
content: correctedContent,
originalContent: originalContent
});
}
updateProgress('zai', 90, 'Finalizing corrections...');
// Generate the ZAI response format
let zaiResponse = `🛠️ ZAI AI CORRECTIONS APPLIED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Successfully corrected ${correctedFiles.length} file(s)
`;
correctedFiles.forEach(file => {
zaiResponse += `
${file.path}
"${file.content}"
`;
});
updateProgress('zai', 100, 'All corrections complete');
return zaiResponse;
} catch (error) {
console.error('AI Correction failed:', error);
throw new Error('Failed to apply AI corrections');
}
}
// Analyze project structure for common issues
function analyzeProjectStructure(files, fileContents, problem) {
const issues = [];
files.forEach(filePath => {
const content = fileContents.get(filePath);
if (!content) return;
// Check for common issues based on file type and problem description
if (filePath.endsWith('.js') || filePath.endsWith('.ts')) {
// JavaScript/TypeScript analysis
if (content.includes('undefined') || problem.toLowerCase().includes('undefined')) {
issues.push({
path: filePath,
issue: 'Potential undefined variable references',
severity: 'high'
});
}
if (!content.includes('try') && problem.toLowerCase().includes('error')) {
issues.push({
path: filePath,
issue: 'Missing error handling',
severity: 'medium'
});
}
if (content.includes('async') && !content.includes('catch') && problem.toLowerCase().includes('async')) {
issues.push({
path: filePath,
issue: 'Async function without error handling',
severity: 'high'
});
}
}
if (filePath.endsWith('.json')) {
try {
JSON.parse(content);
} catch (e) {
issues.push({
path: filePath,
issue: 'Invalid JSON syntax',
severity: 'high'
});
}
}
// Add more analysis rules based on problem keywords
if (problem.toLowerCase().includes('import') || problem.toLowerCase().includes('module')) {
if (filePath.endsWith('.js') && content.includes('require') && !content.includes('module.exports')) {
issues.push({
path: filePath,
issue: 'CommonJS import without proper export',
severity: 'medium'
});
}
}
});
return issues.slice(0, 5); // Limit to 5 files for demo
}
// Fix individual file with AI
async function fixFileWithAI(fileData, originalContent, analysisResult) {
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY' // Replace with actual API key
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [
{
role: 'system',
content: 'You are an expert code fixer. Fix the identified issues in the code while preserving functionality. Return only the corrected code.'
},
{
role: 'user',
content: `File: ${fileData.path}\nIssue: ${fileData.issue}\n\nCode to fix:\n${originalContent}`
}
],
max_tokens: 2000,
temperature: 0.1
})
});
if (!response.ok) {
throw new Error('AI service unavailable');
}
const data = await response.json();
return data.choices[0].message.content;
} catch (error) {
console.error('AI Fix failed:', error);
// Fallback: apply basic fixes
return applyBasicFixes(originalContent, fileData.issue);
}
}
// Apply basic fixes as fallback
function applyBasicFixes(content, issue) {
let fixedContent = content;
if (issue.includes('undefined')) {
// Basic undefined fix
fixedContent = content.replace(/(\w+)\s*=\s*undefined/g, '$1 = null');
}
if (issue.includes('error handling')) {
// Basic try-catch wrapper
if (content.includes('async') && !content.includes('try')) {
fixedContent = content.replace(
/(async\s+\w+\s*\([^)]*\)\s*=>\s*{)/,
'try {$1'
);
fixedContent = fixedContent.replace(
/(})\s*$/,
'} catch (error) { console.error("Error:", error); }'
);
}
}
return fixedContent;
}
// Extract files from AI analysis
function extractFilesFromAnalysis(analysisText) {
const files = [];
const lines = analysisText.split('\n');
lines.forEach(line => {
// Try to extract file paths from the analysis
const match = line.match(/([\w\/\-\.]+\.(js|ts|jsx|tsx|json|html|css))\s*[:\-]/i);
if (match) {
files.push({
path: match[1],
issue: line.substring(match.index + match[0].length).trim()
});
}
});
// If no files found, create based on actual uploaded files
if (files.length === 0) {
const uploadedFiles = Array.from(state.fileContents.keys()).slice(0, 3);
uploadedFiles.forEach(path => {
files.push({
path: path,
issue: 'Potential issues detected'
});
});
}
return files.slice(0, 5);
}
// Parse ZAI response into file objects
function parseZAIFiles(response) {
const files = [];
const fileRegex = /([^\n]+)\n"([^]+?)"(?:\n\n|$)/g;
let match;
while ((match = fileRegex.exec(response)) !== null) {
files.push({
path: match[1],
content: match[2]
});
}
// Store corrected files in state
state.correctedFiles = files;
return files;
}
// Apply changes to the original project
function applyChangesToProject() {
if (!state.correctedFiles || state.correctedFiles.length === 0) {
showError('No corrected files to apply');
return;
}
// Store original contents for comparison
const changesMade = [];
// Apply corrected content to the file contents map
state.correctedFiles.forEach(file => {
const originalContent = state.fileContents.get(file.path);
state.fileContents.set(file.path, file.content);
// Track changes
changesMade.push({
path: file.path,
originalLength: originalContent ? originalContent.length : 0,
newLength: file.content.length,
diff: file.content.length - (originalContent ? originalContent.length : 0)
});
});
// Update UI to show changes applied
elements.appliedStatus.classList.remove('hidden');
elements.appliedSummary.textContent = `${state.correctedFiles.length} file(s) updated successfully`;
elements.viewChanges.classList.remove('hidden');
// Update file list to show applied status
document.querySelectorAll('#file-list .flex').forEach((item, index) => {
if (index < state.correctedFiles.length) {
const statusBadge = document.createElement('span');
statusBadge.className = 'ml-2 px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full';
statusBadge.textContent = 'Applied';
item.appendChild(statusBadge);
// Add checkmark icon
const icon = document.createElement('i');
icon.setAttribute('data-feather', 'check');
icon.className = 'w-4 h-4 text-green-600 ml-2';
item.appendChild(icon);
}
});
feather.replace();
// Store changes for modal view
state.changesMade = changesMade;
showNotification(`AI corrections applied to ${state.correctedFiles.length} file(s)!`, 'success');
// Scroll to applied status
elements.appliedStatus.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
// Render results
function renderResults(files) {
elements.fileList.innerHTML = '';
files.forEach(file => {
const fileElement = document.createElement('div');
fileElement.className = 'flex justify-between items-center bg-gray-50 p-3 rounded-lg';
fileElement.innerHTML = `
<div>
<span class="font-medium text-gray-800">${file.path}</span>
<span class="text-gray-500 text-sm ml-2">(${file.content.length} chars)</span>
</div>
<button class="download-file bg-gray-200 hover:bg-gray-300 rounded-lg p-2" data-path="${file.path}" data-content="${encodeURIComponent(file.content)}">
<i data-feather="download" class="w-4 h-4 text-gray-700"></i>
</button>
`;
elements.fileList.appendChild(fileElement);
});
// Add event listeners to download buttons
document.querySelectorAll('.download-file').forEach(btn => {
btn.addEventListener('click', (e) => {
const path = e.currentTarget.dataset.path;
const content = decodeURIComponent(e.currentTarget.dataset.content);
downloadFile(path, content);
});
});
feather.replace();
elements.resultsContainer.classList.remove('hidden');
}
// Update progress bar
function updateProgress(ai, progress, status) {
if (ai === 'qwen') {
elements.qwenProgress.style.width = `${progress}%`;
elements.qwenStatus.textContent = status;
} else if (ai === 'zai') {
elements.zaiProgress.style.width = `${progress}%`;
elements.zaiStatus.textContent = status;
}
}
// Download a single file
function downloadFile(path, content) {
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = path.split('/').pop();
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Download all files
async function downloadAllFiles() {
if (state.correctedFiles && state.correctedFiles.length > 0) {
// Download only the corrected files
state.correctedFiles.forEach(file => {
downloadFile(file.path, file.content);
});
showNotification(`Downloaded ${state.correctedFiles.length} AI-corrected file(s)`, 'success');
} else {
showError('No corrected files available for download');
}
}
// Show changes summary modal
function showChangesSummary() {
if (!state.changesMade || state.changesMade.length === 0) {
showError('No changes to display');
return;
}
elements.changesContent.innerHTML = '';
state.changesMade.forEach(change => {
const changeElement = document.createElement('div');
changeElement.className = 'border rounded-lg p-4 bg-gray-50';
const diffClass = change.diff > 0 ? 'text-green-600' : change.diff < 0 ? 'text-red-600' : 'text-gray-600';
const diffSymbol = change.diff > 0 ? '+' : change.diff < 0 ? '-' : '';
changeElement.innerHTML = `
<div class="flex justify-between items-start mb-2">
<h4 class="font-semibold text-gray-800">${change.path}</h4>
<span class="${diffClass} font-medium">${diffSymbol}${change.diff} chars</span>
</div>
<div class="text-sm text-gray-600">
<p>Original: ${change.originalLength} characters</p>
<p>New: ${change.newLength} characters</p>
<p class="mt-2 text-xs text-gray-500">
${change.diff > 0 ? 'Content added' : change.diff < 0 ? 'Content removed' : 'No size change'}
</p>
</div>
`;
elements.changesContent.appendChild(changeElement);
});
elements.changesModal.classList.remove('hidden');
}
// Hide changes summary modal
function hideChangesSummary() {
elements.changesModal.classList.add('hidden');
}
// Show notification
function showNotification(message, type = 'info') {
if (type === 'error') {
elements.errorMessage.textContent = message;
elements.errorContainer.classList.remove('hidden');
setTimeout(() => {
elements.errorContainer.classList.add('hidden');
}, 5000);
} else if (type === 'success') {
elements.successMessage.textContent = message;
elements.successContainer.classList.remove('hidden');
setTimeout(() => {
elements.successContainer.classList.add('hidden');
}, 5000);
} else {
// Info notification - create temporary element
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 px-6 py-4 rounded-lg shadow-lg text-white z-50 flex items-center bg-blue-500';
notification.innerHTML = `
<i data-feather="info" class="w-5 h-5 mr-3"></i>
<span>${message}</span>
`;
document.body.appendChild(notification);
feather.replace();
setTimeout(() => {
notification.remove();
}, 3000);
}
}
// Show error
function showError(message) {
showNotification(message, 'error');
}
// Download corrected file
function downloadCorrectedFile(file) {
const blob = new Blob([file.content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file.path.split('/').pop();
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification(`Arquivo ${file.path} baixado!`, 'success');
}
// Download all corrected files
function downloadAllCorrectedFiles() {
state.correctedFiles.forEach(file => {
downloadCorrectedFile(file);
});
}
// Generate Git patch
function generateGitPatch() {
let patch = '';
state.correctedFiles.forEach(file => {
const originalContent = state.fileContents.get(file.path) || '';
patch += `--- a/${file.path}\n`;
patch += `+++ b/${file.path}\n`;
patch += `@@ -1,1 +1,1 @@\n`;
// Simple diff - in real implementation, use a proper diff library
const originalLines = originalContent.split('\n');
const newLines = file.content.split('\n');
// Remove lines
originalLines.forEach(line => {
if (!newLines.includes(line)) {
patch += `-${line}\n`;
}
});
// Add lines
newLines.forEach(line => {
if (!originalLines.includes(line)) {
patch += `+${line}\n`;
}
});
patch += '\n';
});
const blob = new Blob([patch], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'corrections.patch';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification('Patch Git gerado com sucesso!', 'success');
}
// Reset application
function resetApplication() {
if (confirm('Tem certeza que deseja iniciar um novo projeto? Todos os dados não salvos serão perdidos.')) {
state.files = [];
state.fileContents.clear();
state.selectedFiles.clear();
state.processedText = '';
state.qwenResponse = '';
state.zaiResponse = '';
state.correctedFiles = [];
state.filesNeedingFix = [];
state.fileTreeStructure = null;
// Reset UI
elements.fileTreeContainer.classList.add('hidden');
elements.reportContainer.classList.add('hidden');
elements.resultsSummary.classList.add('hidden');
elements.qwenAnalysis.textContent = '';
elements.zaiCorrections.textContent = '';
// Reset progress bars
updateProgress('qwen', 0, 'Aguardando');
updateProgress('zai', 0, 'Aguardando');
// Switch to upload tab
document.querySelector('[data-tab="upload"]').click();
showNotification('Aplicação reiniciada!', 'success');
}
}
// Show analysis details
function showAnalysisDetails(analysisText) {
elements.qwenAnalysis.innerHTML = `
<div class="whitespace-pre-wrap">${analysisText}</div>
`;
elements.analysisOutput.classList.remove('hidden');
}