├── capture112.PNG ├── capture113.PNG ├── capture114.PNG ├── styles.css ├── README.md ├── index.html └── script.js /capture112.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohittiwari98/Code-review-using-AI/main/capture112.PNG -------------------------------------------------------------------------------- /capture113.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohittiwari98/Code-review-using-AI/main/capture113.PNG -------------------------------------------------------------------------------- /capture114.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohittiwari98/Code-review-using-AI/main/capture114.PNG -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | @keyframes slideIn { 2 | from { opacity: 0; transform: translateY(30px); } 3 | to { opacity: 1; transform: translateY(0); } 4 | } 5 | .slide-in { 6 | animation: slideIn 0.6s ease-out; 7 | } 8 | .spinner { 9 | border: 4px solid rgba(255, 255, 255, 0.2); 10 | border-left-color: #8b5cf6; 11 | border-radius: 50%; 12 | width: 28px; 13 | height: 28px; 14 | animation: spin 1s linear infinite; 15 | display: inline-block; 16 | } 17 | @keyframes spin { 18 | to { transform: rotate(360deg); } 19 | } 20 | .glass { 21 | background: rgba(255, 255, 255, 0.15); 22 | backdrop-filter: blur(12px); 23 | border: 1px solid rgba(255, 255, 255, 0.3); 24 | box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2); 25 | transition: transform 0.3s ease, box-shadow 0.3s ease; 26 | } 27 | .glass:hover { 28 | transform: scale(1.02); 29 | box-shadow: 0 8px 40px rgba(0, 0, 0, 0.3); 30 | } 31 | button { 32 | transition: transform 0.2s ease, background 0.3s ease; 33 | } 34 | button:hover { 35 | transform: scale(1.05); 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code-review-using-AI 2 | Introduction:- 3 | A web app that analyzes code snippets (Python, JavaScript, Java, C++,C pro) using the Gemini API for feedback on style, bugs, and optimizations. Features a glassmorphism UI, Chart.js radar chart, PDF export, confetti, dark mode, and accessibility. 4 | 5 | Features 6 | 7 | Analyzes code with Gemini API, providing a 0-100% quality score. 8 | 9 | Displays feedback and metrics (readability, complexity, correctness, efficiency). 10 | 11 | Visualizes metrics with a colorful radar chart. 12 | 13 | Exports reports as PDF (jsPDF). 14 | 15 | Includes confetti for scores ≥80%, dark mode, and ARIA accessibility. 16 | 17 | Saves results to LocalStorage. 18 | 19 | 20 | Usage 21 | 22 | Select language, paste code (e.g., Python factorial), and click “Review Code” or press Ctrl+Enter. 23 | 24 | View score, feedback, radar chart, and code snippet. 25 | 26 | Download PDF report or copy code. 27 | 28 | Toggle dark mode and test accessibility with NVDA. 29 | 30 | Project Structure 31 | 32 | code-review-assistant/ 33 | 34 | ├── index.html 35 | 36 | ├── styles.css 37 | 38 | ├── script.js 39 | 40 | └── README.md 41 | 42 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AI Code Review Assistant 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

AI Code Review Assistant

17 | 22 |
23 |
24 |

Submit Code for Review

25 |
26 | 32 | 33 | 34 |
35 | 38 |
39 |
40 |
41 |

Code Review Results

42 | 51 | 52 |
53 |
54 |

Code Snippet

55 | 56 | 57 |
58 |
59 |
60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | //script file or code 2 | //use api key from Gemini 3 | //also use site url 4 | const API_KEY = ""; 5 | const API_URL = ""; 6 | const { jsPDF } = window.jspdf; 7 | let metricsChart; 8 | 9 | //form toggleTheme 10 | const themeToggle = document.getElementById('theme-toggle'); 11 | if (themeToggle) { 12 | themeToggle.addEventListener('click', () => { 13 | document.body.classList.toggle('dark'); 14 | localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); 15 | }); 16 | // store in local storage 17 | if (localStorage.getItem('theme') === 'dark') { 18 | document.body.classList.add('dark'); 19 | } 20 | } 21 | 22 | 23 | //generate renderchart 24 | function renderChart(metrics) { 25 | if (!document.getElementById('metrics-chart')) { 26 | console.error('Metrics chart canvas not found'); 27 | return; 28 | } 29 | if (metricsChart) metricsChart.destroy(); 30 | 31 | // 32 | const ctx = document.getElementById('metrics-chart').getContext('2d'); 33 | metricsChart = new Chart(ctx, { 34 | type: 'radar', 35 | data: { 36 | labels: ['Readability', 'Complexity', 'Correctness', 'Efficiency'], 37 | datasets: [{ 38 | label: 'Code Quality Metrics', 39 | data: [ 40 | metrics.readability || 50, 41 | metrics.complexity || 50, 42 | metrics.correctness || 50, 43 | metrics.efficiency || 50 44 | ], 45 | backgroundColor: 'rgba(139, 92, 246, 0.2)', 46 | borderColor: '#8b5cf6', 47 | pointBackgroundColor: '#3b82f6', 48 | pointBorderColor: '#ffffff', 49 | pointHoverBackgroundColor: '#ec4899' 50 | }] 51 | }, 52 | options: { 53 | responsive: true, 54 | plugins: { 55 | title: { display: true, text: 'Code Quality Metrics', font: { size: 16, weight: 'bold' }, color: '#1f2937' }, 56 | legend: { position: 'bottom', labels: { color: '#1f2937' } }, 57 | tooltip: { backgroundColor: '#1f2937', titleColor: '#ffffff', bodyColor: '#ffffff' } 58 | }, 59 | scales: { 60 | r: { 61 | angleLines: { color: '#e5e7eb' }, 62 | grid: { color: '#e5e7eb' }, 63 | pointLabels: { font: { size: 14 }, color: '#1f2937' }, 64 | ticks: { beginAtZero: true, max: 100, stepSize: 20, color: '#1f2937' } 65 | } 66 | } 67 | } 68 | }); 69 | } 70 | 71 | 72 | async function fetchWithRetry(url, options, retries = 3, backoff = 1000) { 73 | for (let i = 0; i < retries; i++) { 74 | try { 75 | const response = await fetch(url, options); 76 | if (response.status === 429) { 77 | const delay = backoff * Math.pow(2, i); 78 | console.warn(`Rate limit hit for ${url}, retrying after ${delay}ms`); 79 | await new Promise(resolve => setTimeout(resolve, delay)); 80 | continue; 81 | } 82 | if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); 83 | return response; 84 | } catch (error) { 85 | if (i === retries - 1) throw error; 86 | } 87 | } 88 | throw new Error('Max retries reached'); 89 | } 90 | 91 | 92 | async function reviewCode() { 93 | const codeInput = document.getElementById('code-input'); 94 | const languageSelect = document.getElementById('language'); 95 | const reviewResult = document.getElementById('review-result'); 96 | const loadingDiv = document.getElementById('loading'); 97 | const codeDisplay = document.getElementById('code-display'); 98 | const copyButton = document.getElementById('copy-code'); 99 | const downloadButton = document.getElementById('download-pdf'); 100 | 101 | if (!codeInput || !languageSelect || !reviewResult || !loadingDiv || !codeDisplay || !copyButton || !downloadButton) { 102 | console.error('DOM elements missing:', { codeInput, languageSelect, reviewResult, loadingDiv, codeDisplay, copyButton, downloadButton }); 103 | alert('Error: Page elements not loaded. Please refresh.'); 104 | return; 105 | } 106 | 107 | const code = codeInput.value.trim(); 108 | const language = languageSelect.value; 109 | if (!code) { 110 | alert('Please enter a code snippet!'); 111 | return; 112 | } 113 | 114 | loadingDiv.classList.remove('hidden'); 115 | reviewResult.classList.add('hidden'); 116 | codeDisplay.classList.add('hidden'); 117 | copyButton.classList.add('hidden'); 118 | downloadButton.classList.add('hidden'); 119 | 120 | try { 121 | 122 | const prompt = ` 123 | Analyze the following ${language} code snippet for style, potential bugs, and optimizations: 124 | \`\`\` 125 | ${code} 126 | \`\`\` 127 | 128 | Provide: 129 | - A quality score (0-100%) based on readability, complexity, correctness, and efficiency. 130 | - A short feedback summary (50-70 words) explaining the score. 131 | - 3-5 bullet points detailing strengths and areas for improvement. 132 | - Scores (0-100) for readability, complexity, correctness, and efficiency. 133 | 134 | Return the response in JSON format: 135 | { 136 | "score": number, 137 | "feedback": string, 138 | "details": string[], 139 | "metrics": { 140 | "readability": number, 141 | "complexity": number, 142 | "correctness": number, 143 | "efficiency": number 144 | } 145 | } 146 | `; 147 | 148 | 149 | const response = await fetchWithRetry(`${API_URL}?key=${API_KEY}`, { 150 | method: 'POST', 151 | headers: { 'Content-Type': 'application/json' }, 152 | body: JSON.stringify({ 153 | contents: [{ parts: [{ text: prompt }] }], 154 | generationConfig: { response_mime_type: 'application/json' } 155 | }) 156 | }); 157 | 158 | const data = await response.json(); 159 | let result; 160 | try { 161 | result = JSON.parse(data.candidates?.[0]?.content?.parts?.[0]?.text || '{}'); 162 | } catch (error) { 163 | console.warn('Failed to parse Gemini response:', error); 164 | result = { 165 | score: 60, 166 | feedback: 'No feedback available due to API parsing error.', 167 | details: ['No details provided.'], 168 | metrics: { readability: 50, complexity: 50, correctness: 50, efficiency: 50 } 169 | }; 170 | } 171 | 172 | const { score = 60, feedback = 'No feedback provided.', details = ['No details available.'], metrics = { readability: 50, complexity: 50, correctness: 50, efficiency: 50 } } = result; 173 | 174 | // Update UI 175 | document.getElementById('result-score').textContent = `Quality Score: ${score}%`; 176 | document.getElementById('result-feedback').textContent = feedback; 177 | document.getElementById('result-details').innerHTML = details.map(detail => `
  • ${detail}
  • `).join(''); 178 | codeDisplay.textContent = code; 179 | codeDisplay.classList.remove('hidden'); 180 | reviewResult.classList.remove('hidden'); 181 | reviewResult.classList.add('slide-in'); 182 | copyButton.classList.remove('hidden'); 183 | downloadButton.classList.remove('hidden'); 184 | 185 | // Render chart 186 | renderChart(metrics); 187 | 188 | // Save to localStorage 189 | try { 190 | localStorage.setItem('codeReviewData', JSON.stringify({ 191 | code, 192 | language, 193 | score, 194 | feedback, 195 | details, 196 | metrics 197 | })); 198 | } catch (error) { 199 | console.error('Failed to save to localStorage:', error); 200 | } 201 | 202 | // Confetti for high scores 203 | if (score >= 80) { 204 | confetti({ particleCount: 150, spread: 80, colors: ['#3b82f6', '#ec4899', '#8b5cf6'], origin: { y: 0.6 } }); 205 | } 206 | 207 | setTimeout(() => { 208 | reviewResult.classList.remove('slide-in'); 209 | codeDisplay.classList.remove('slide-in'); 210 | }, 600); 211 | } catch (error) { 212 | console.error('Error in reviewCode:', error); 213 | codeDisplay.textContent = `Error: Failed to review code. ${error.message.includes('429') ? 'API rate limit exceeded. Please try again later.' : error.message}`; 214 | codeDisplay.classList.remove('hidden'); 215 | codeDisplay.classList.add('slide-in'); 216 | setTimeout(() => codeDisplay.classList.remove('slide-in'), 600); 217 | } finally { 218 | loadingDiv.classList.add('hidden'); 219 | } 220 | } 221 | 222 | // Download PDF 223 | document.getElementById('download-pdf').addEventListener('click', () => { 224 | const reviewResult = document.getElementById('review-result'); 225 | if (!reviewResult || reviewResult.classList.contains('hidden')) { 226 | alert('Review code first!'); 227 | return; 228 | } 229 | try { 230 | const doc = new jsPDF(); 231 | doc.setFontSize(16); 232 | doc.text('Code Review Report', 10, 10); 233 | doc.setFontSize(12); 234 | doc.text(document.getElementById('result-score')?.textContent || 'Score: Unknown', 10, 20); 235 | doc.setFontSize(14); 236 | doc.text('Feedback', 10, 30); 237 | doc.setFontSize(10); 238 | doc.text(document.getElementById('result-feedback')?.textContent || 'No feedback', 10, 40, { maxWidth: 190 }); 239 | doc.setFontSize(14); 240 | doc.text('Details', 10, 70); 241 | doc.setFontSize(10); 242 | const details = Array.from(document.getElementById('result-details')?.children || []).map(li => li.textContent); 243 | details.forEach((detail, i) => doc.text(`- ${detail}`, 10, 80 + i * 10)); 244 | doc.setFontSize(14); 245 | doc.text('Code Snippet', 10, 80 + details.length * 10 + 10); 246 | doc.setFontSize(10); 247 | doc.text(document.getElementById('code-display')?.textContent || 'No code', 10, 90 + details.length * 10, { maxWidth: 190 }); 248 | doc.setFontSize(14); 249 | doc.text('Metrics', 10, 90 + details.length * 10 + 30); 250 | doc.setFontSize(10); 251 | let metrics = {}; 252 | try { 253 | metrics = JSON.parse(localStorage.getItem('codeReviewData'))?.metrics || {}; 254 | } catch (error) { 255 | console.warn('Failed to parse metrics from localStorage:', error); 256 | } 257 | const metricsText = Object.entries(metrics).map(([key, value]) => `${key}: ${value}%`).join(', '); 258 | doc.text(metricsText || 'No metrics', 10, 100 + details.length * 10 + 30, { maxWidth: 190 }); 259 | doc.save('code_review.pdf'); 260 | confetti({ particleCount: 150, spread: 80, colors: ['#3b82f6', '#ec4899', '#8b5cf6'], origin: { y: 0.6 } }); 261 | } catch (error) { 262 | console.error('PDF generation failed:', error); 263 | alert('Failed to generate PDF.'); 264 | } 265 | }); 266 | 267 | // Copy code 268 | document.getElementById('copy-code').addEventListener('click', () => { 269 | const codeDisplay = document.getElementById('code-display'); 270 | if (codeDisplay) { 271 | navigator.clipboard.writeText(codeDisplay.textContent).then(() => { 272 | alert('Code copied to clipboard!'); 273 | }).catch(err => { 274 | console.error('Copy failed:', err); 275 | alert('Failed to copy code.'); 276 | }); 277 | } 278 | }); 279 | 280 | // Load saved data 281 | document.addEventListener('DOMContentLoaded', () => { 282 | let savedData; 283 | try { 284 | savedData = JSON.parse(localStorage.getItem('codeReviewData')); 285 | } catch (error) { 286 | console.warn('Failed to parse localStorage:', error); 287 | return; 288 | } 289 | if (savedData) { 290 | const elements = { 291 | codeInput: document.getElementById('code-input'), 292 | language: document.getElementById('language'), 293 | resultScore: document.getElementById('result-score'), 294 | resultFeedback: document.getElementById('result-feedback'), 295 | resultDetails: document.getElementById('result-details'), 296 | codeDisplay: document.getElementById('code-display'), 297 | reviewResult: document.getElementById('review-result'), 298 | copyCode: document.getElementById('copy-code'), 299 | downloadPdf: document.getElementById('download-pdf') 300 | }; 301 | if (Object.values(elements).some(el => !el)) { 302 | console.error('DOM elements missing during saved data load:', elements); 303 | return; 304 | } 305 | elements.codeInput.value = savedData.code; 306 | elements.language.value = savedData.language; 307 | elements.resultScore.textContent = `Quality Score: ${savedData.score}%`; 308 | elements.resultFeedback.textContent = savedData.feedback; 309 | elements.resultDetails.innerHTML = savedData.details.map(detail => `
  • ${detail}
  • `).join(''); 310 | elements.codeDisplay.textContent = savedData.code; 311 | elements.reviewResult.classList.remove('hidden'); 312 | elements.codeDisplay.classList.remove('hidden'); 313 | elements.copyCode.classList.remove('hidden'); 314 | elements.downloadPdf.classList.remove('hidden'); 315 | renderChart(savedData.metrics || { readability: 50, complexity: 50, correctness: 50, efficiency: 50 }); 316 | setTimeout(() => { 317 | elements.reviewResult.classList.remove('slide-in'); 318 | elements.codeDisplay.classList.remove('slide-in'); 319 | }, 600); 320 | } 321 | }); 322 | 323 | // Keyboard navigation 324 | const codeInput = document.getElementById('code-input'); 325 | if (codeInput) { 326 | codeInput.addEventListener('keydown', (e) => { 327 | if (e.ctrlKey && e.key === 'Enter') reviewCode(); 328 | }); 329 | } 330 | --------------------------------------------------------------------------------