├── 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 |
17 |
22 |
23 |
24 |
Submit Code for Review
25 |
26 |
32 |
33 |
34 |
35 |
36 | Analyzing Code...
37 |
38 |
39 |
40 |
41 |
Code Review Results
42 |
43 |
44 |
Feedback
45 |
46 |
Details
47 |
48 |
Quality Metrics
49 |
50 |
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 |
--------------------------------------------------------------------------------