Spaces:
Running
Running
| // 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'); | |
| } | |