Spaces:
Sleeping
Sleeping
lakshmisravya123 commited on
Commit ·
273bb97
1
Parent(s): 1ee4c18
Major upgrade: comprehensive code analysis with security, performance, clean code scoring, and test suggestions
Browse files- backend/public/assets/index-BFNcLyEY.css +1 -0
- backend/public/assets/index-D1n-RmpM.js +0 -0
- backend/public/index.html +7 -0
- backend/services/ai.js +81 -12
- frontend/src/App.jsx +296 -40
- frontend/src/styles/global.css +50 -15
backend/public/assets/index-BFNcLyEY.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*{margin:0;padding:0;box-sizing:border-box}body{font-family:Courier New,monospace;background:#0d1117;color:#c9d1d9;min-height:100vh}.app{max-width:900px;margin:0 auto;padding:2rem 1rem}h1{text-align:center;font-size:2.5rem;margin-bottom:.5rem}h1 span{background:linear-gradient(135deg,#f97316,#ef4444);-webkit-background-clip:text;-webkit-text-fill-color:transparent}.subtitle{text-align:center;color:#8b949e;margin-bottom:2rem;font-style:italic}.editor-section{background:#161b22;border-radius:12px;padding:1.5rem;margin-bottom:1.5rem;border:1px solid #30363d}.editor-section label{display:block;margin-bottom:.5rem;color:#8b949e}.editor-section select{padding:.5rem;border-radius:6px;border:1px solid #30363d;background:#0d1117;color:#c9d1d9;margin-bottom:1rem}.code-input{width:100%;min-height:200px;padding:1rem;border:1px solid #30363d;border-radius:8px;background:#0d1117;color:#79c0ff;font-family:Courier New,monospace;font-size:.9rem;resize:vertical;-moz-tab-size:2;tab-size:2}.btn{width:100%;padding:1rem;border:none;border-radius:8px;font-size:1.1rem;font-weight:700;cursor:pointer;background:linear-gradient(135deg,#f97316,#ef4444);color:#fff;font-family:inherit;transition:transform .2s}.btn:hover{transform:scale(1.02)}.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.loading{text-align:center;padding:3rem}.loading .spinner{width:50px;height:50px;border:4px solid #30363d;border-top-color:#f97316;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 1rem}@keyframes spin{to{transform:rotate(360deg)}}.results{margin-top:2rem}.roast-card{background:#161b22;border-radius:12px;padding:1.5rem;margin-bottom:1.5rem;border:1px solid #30363d}.opening-roast{font-size:1.2rem;color:#f97316;font-style:italic;text-align:center;padding:1.5rem;background:linear-gradient(135deg,#161b22,#1c1206);border-radius:12px;border:1px solid #f97316;margin-bottom:1.5rem}.tab-nav{display:flex;gap:.3rem;margin-bottom:1.5rem;overflow-x:auto;padding-bottom:.5rem;border-bottom:1px solid #30363d}.tab-btn{padding:.5rem 1rem;border:1px solid transparent;border-bottom:none;border-radius:8px 8px 0 0;background:transparent;color:#8b949e;font-family:inherit;font-size:.85rem;cursor:pointer;white-space:nowrap;transition:all .2s}.tab-btn:hover{color:#c9d1d9;background:#161b22}.tab-btn.active{color:#f97316;background:#161b22;border-color:#30363d;border-bottom:2px solid #f97316;font-weight:700}.score-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;margin-bottom:1.5rem}.score-card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:1.5rem;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center}.score-big{font-size:3.5rem;font-weight:700;color:#f97316;line-height:1}.hire-badge{display:inline-block;padding:.4rem 1.2rem;border-radius:20px;font-weight:700;font-size:.9rem}.hire-badge.yes{background:#238636;color:#fff}.hire-badge.maybe{background:#9e6a03;color:#fff}.hire-badge.no{background:#da3633;color:#fff}.issue-card{background:#0d1117;border-radius:8px;padding:1rem;margin-bottom:.8rem;border-left:3px solid #f97316}.issue-card .roast-text{color:#f97316;font-style:italic;margin-bottom:.5rem}.issue-card .fix-text{color:#7ee787}.vuln-card{background:#0d1117;border-radius:8px;padding:1rem;margin-bottom:.8rem;border-left:3px solid #da3633}.complexity-badge{background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:.5rem 1rem;text-align:center}.test-card{background:#0d1117;border-radius:8px;padding:1rem;margin-bottom:.8rem;border-left:3px solid #388bfd}.test-type{display:inline-block;padding:.15rem .6rem;border-radius:10px;font-size:.75rem;font-weight:700;text-transform:uppercase}.test-unit{background:#238636;color:#fff}.test-integration{background:#9e6a03;color:#fff}.test-edge-case{background:#388bfd;color:#fff}.tag{display:inline-block;padding:.2rem .6rem;border-radius:12px;background:#30363d;color:#8b949e;font-size:.8rem;margin:.2rem}.tag-warn{background:#3d2e00;color:#d29922;border:1px solid #9e6a03}.tag-error{background:#3d0000;color:#f85149;border:1px solid #da3633}.rewritten-code{background:#0d1117;border:1px solid #238636;border-radius:8px;padding:1rem;overflow-x:auto;white-space:pre-wrap;color:#7ee787;font-size:.85rem;max-height:500px;overflow-y:auto}.section-title{color:#f97316;margin-bottom:.8rem;font-size:1.1rem;font-weight:700}.closing-roast{font-size:1.1rem;color:#ef4444;font-style:italic;text-align:center;padding:1.5rem;margin:1.5rem 0;background:linear-gradient(135deg,#161b22,#1c0606);border-radius:12px;border:1px solid #ef4444}.back-btn{background:#30363d;margin-top:1rem}.back-btn:hover{background:#484f58}@media(max-width:768px){h1{font-size:1.8rem}.score-big{font-size:2.5rem}.score-grid{grid-template-columns:1fr}.tab-nav{gap:.2rem}.tab-btn{padding:.4rem .6rem;font-size:.75rem}.roast-card{padding:1rem}}
|
backend/public/assets/index-D1n-RmpM.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
backend/public/index.html
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Code Roast Battle</title> <script type="module" crossorigin src="/assets/index-D1n-RmpM.js"></script>
|
| 4 |
+
<link rel="stylesheet" crossorigin href="/assets/index-BFNcLyEY.css">
|
| 5 |
+
</head>
|
| 6 |
+
<body><div id="root"></div></body>
|
| 7 |
+
</html>
|
backend/services/ai.js
CHANGED
|
@@ -8,7 +8,7 @@ async function callAI(prompt) {
|
|
| 8 |
const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
|
| 9 |
method: 'POST',
|
| 10 |
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${GROQ_API_KEY}` },
|
| 11 |
-
body: JSON.stringify({ model: GROQ_MODEL, messages: [{ role: 'user', content: prompt }], temperature: 0.8 }),
|
| 12 |
});
|
| 13 |
if (res.ok) { const data = await res.json(); return data.choices[0].message.content; }
|
| 14 |
console.warn('Groq failed, falling back to Ollama...');
|
|
@@ -24,35 +24,104 @@ async function callAI(prompt) {
|
|
| 24 |
|
| 25 |
function parseJSON(text) {
|
| 26 |
try { return JSON.parse(text.trim()); }
|
| 27 |
-
catch {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
| 29 |
-
|
| 30 |
async function roastCode(code, language) {
|
| 31 |
-
const
|
| 32 |
|
| 33 |
-
LANGUAGE: ${language ||
|
| 34 |
-
CODE:
|
| 35 |
\`\`\`
|
| 36 |
${code}
|
| 37 |
\`\`\`
|
| 38 |
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
{
|
| 41 |
"language": "<detected language>",
|
| 42 |
"overallScore": <1-100>,
|
|
|
|
| 43 |
"roastLevel": "<raw|medium-rare|well-done|burnt-to-a-crisp>",
|
| 44 |
-
"openingRoast": "<A 2-3 sentence Gordon Ramsay style opening roast
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
"issues": [
|
| 46 |
{"line": "<line or section>", "roast": "<funny roast of this issue>", "fix": "<actual helpful fix>", "severity": "<mild|spicy|nuclear>"}
|
| 47 |
],
|
| 48 |
-
"
|
| 49 |
-
"bestPracticeViolations": ["<violation 1>", "<violation 2>"],
|
| 50 |
-
"rewrittenCode": "<the entire code rewritten properly with best practices>",
|
| 51 |
"closingRoast": "<A final dramatic one-liner, Gordon Ramsay style>",
|
| 52 |
"wouldHire": "<YES|MAYBE|GET OUT OF MY KITCHEN>"
|
| 53 |
}
|
| 54 |
|
| 55 |
-
Return ONLY JSON, no
|
|
|
|
| 56 |
return parseJSON(text);
|
| 57 |
}
|
| 58 |
|
|
|
|
| 8 |
const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
|
| 9 |
method: 'POST',
|
| 10 |
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${GROQ_API_KEY}` },
|
| 11 |
+
body: JSON.stringify({ model: GROQ_MODEL, messages: [{ role: 'user', content: prompt }], temperature: 0.8, max_tokens: 4096 }),
|
| 12 |
});
|
| 13 |
if (res.ok) { const data = await res.json(); return data.choices[0].message.content; }
|
| 14 |
console.warn('Groq failed, falling back to Ollama...');
|
|
|
|
| 24 |
|
| 25 |
function parseJSON(text) {
|
| 26 |
try { return JSON.parse(text.trim()); }
|
| 27 |
+
catch {
|
| 28 |
+
const m = text.match(/\{[\s\S]*\}/);
|
| 29 |
+
if (m) return JSON.parse(m[0]);
|
| 30 |
+
throw new Error('Failed to parse AI response');
|
| 31 |
+
}
|
| 32 |
}
|
|
|
|
| 33 |
async function roastCode(code, language) {
|
| 34 |
+
const prompt = `You are "Chef CodeRamsay" - a Gordon Ramsay-style code reviewer who is also a world-class security researcher, performance engineer, and clean-code evangelist. You roast bad code with sharp wit, creative insults, and dramatic reactions. But you ALSO provide deep, genuinely expert-level analysis.
|
| 35 |
|
| 36 |
+
LANGUAGE: ${language || "auto-detect"}
|
| 37 |
+
CODE TO REVIEW:
|
| 38 |
\`\`\`
|
| 39 |
${code}
|
| 40 |
\`\`\`
|
| 41 |
|
| 42 |
+
Perform a COMPREHENSIVE code review covering ALL of these areas:
|
| 43 |
+
|
| 44 |
+
1. SECURITY VULNERABILITIES: Check for SQL injection, XSS, CSRF, hardcoded secrets/API keys, insecure deserialization, path traversal, command injection, insecure random, missing input validation, and any OWASP Top 10 issues.
|
| 45 |
+
|
| 46 |
+
2. PERFORMANCE ANALYSIS: Evaluate time complexity (Big O), memory usage, unnecessary loops or re-renders, N+1 queries, inefficient data structures, missing caching opportunities, blocking operations.
|
| 47 |
+
|
| 48 |
+
3. BEST PRACTICES PER LANGUAGE: Evaluate naming conventions, error handling patterns, proper use of language idioms, type safety, documentation, consistent style, SOLID principles adherence.
|
| 49 |
+
|
| 50 |
+
4. CODE SMELLS with severity: Identify code smells like long methods, god classes, magic numbers, deep nesting, code duplication, feature envy, shotgun surgery, etc. Rate each as info/warning/critical.
|
| 51 |
+
|
| 52 |
+
5. CLEAN CODE SCORE: Rate based on Robert C. Martin Clean Code principles - meaningful names, small functions, single responsibility, DRY, no side effects, command-query separation, etc. Score 0-100.
|
| 53 |
+
|
| 54 |
+
6. MAINTAINABILITY INDEX: Score 0-100 based on cyclomatic complexity, lines of code, coupling, cohesion, and readability.
|
| 55 |
+
|
| 56 |
+
7. TEST COVERAGE SUGGESTIONS: What unit tests, integration tests, and edge case tests should be written for this code?
|
| 57 |
+
|
| 58 |
+
8. DEPENDENCY/IMPORT ANALYSIS: Are imports used? Are there missing imports? Are there better alternatives to current dependencies? Any circular dependencies?
|
| 59 |
+
|
| 60 |
+
9. OVERALL LETTER GRADE: Rate A+ through F with justification.
|
| 61 |
+
Return ONLY valid JSON (no markdown, no backticks):
|
| 62 |
{
|
| 63 |
"language": "<detected language>",
|
| 64 |
"overallScore": <1-100>,
|
| 65 |
+
"letterGrade": "<A+|A|A-|B+|B|B-|C+|C|C-|D+|D|D-|F>",
|
| 66 |
"roastLevel": "<raw|medium-rare|well-done|burnt-to-a-crisp>",
|
| 67 |
+
"openingRoast": "<A 2-3 sentence Gordon Ramsay style opening roast. Be dramatic, creative, and funny.>",
|
| 68 |
+
"securityAnalysis": {
|
| 69 |
+
"score": <0-100>,
|
| 70 |
+
"vulnerabilities": [
|
| 71 |
+
{"type": "<e.g. SQL Injection, XSS, Hardcoded Secret>", "severity": "<critical|high|medium|low>", "description": "<what the vulnerability is>", "location": "<line or section>", "fix": "<how to fix it>", "roast": "<Gordon Ramsay comment about this security flaw>"}
|
| 72 |
+
],
|
| 73 |
+
"summary": "<1-2 sentence security summary>"
|
| 74 |
+
},
|
| 75 |
+
"performanceAnalysis": {
|
| 76 |
+
"score": <0-100>,
|
| 77 |
+
"timeComplexity": "<e.g. O(n^2)>",
|
| 78 |
+
"spaceComplexity": "<e.g. O(n)>",
|
| 79 |
+
"issues": [
|
| 80 |
+
{"issue": "<performance problem>", "impact": "<high|medium|low>", "suggestion": "<how to optimize>"}
|
| 81 |
+
],
|
| 82 |
+
"summary": "<1-2 sentence performance summary>"
|
| 83 |
+
},
|
| 84 |
+
"bestPractices": {
|
| 85 |
+
"score": <0-100>,
|
| 86 |
+
"violations": [
|
| 87 |
+
{"practice": "<what best practice is violated>", "severity": "<critical|warning|info>", "description": "<details>", "fix": "<recommendation>"}
|
| 88 |
+
]
|
| 89 |
+
},
|
| 90 |
+
"codeSmells": [
|
| 91 |
+
{"smell": "<name of code smell>", "severity": "<critical|warning|info>", "location": "<where>", "description": "<why it smells>"}
|
| 92 |
+
],
|
| 93 |
+
"cleanCodeScore": <0-100>,
|
| 94 |
+
"cleanCodeBreakdown": {
|
| 95 |
+
"meaningfulNames": <0-10>,
|
| 96 |
+
"smallFunctions": <0-10>,
|
| 97 |
+
"singleResponsibility": <0-10>,
|
| 98 |
+
"dryPrinciple": <0-10>,
|
| 99 |
+
"errorHandling": <0-10>,
|
| 100 |
+
"readability": <0-10>,
|
| 101 |
+
"formatting": <0-10>,
|
| 102 |
+
"comments": <0-10>,
|
| 103 |
+
"noSideEffects": <0-10>,
|
| 104 |
+
"testability": <0-10>
|
| 105 |
+
},
|
| 106 |
+
"maintainabilityIndex": <0-100>,
|
| 107 |
+
"testSuggestions": [
|
| 108 |
+
{"type": "<unit|integration|edge-case>", "description": "<what test to write>", "priority": "<high|medium|low>"}
|
| 109 |
+
],
|
| 110 |
+
"dependencyAnalysis": {
|
| 111 |
+
"unusedImports": ["<unused import 1>"],
|
| 112 |
+
"missingImports": ["<missing import 1>"],
|
| 113 |
+
"suggestions": ["<suggestion about dependencies>"]
|
| 114 |
+
},
|
| 115 |
"issues": [
|
| 116 |
{"line": "<line or section>", "roast": "<funny roast of this issue>", "fix": "<actual helpful fix>", "severity": "<mild|spicy|nuclear>"}
|
| 117 |
],
|
| 118 |
+
"rewrittenCode": "<the entire code rewritten properly with best practices, security fixes, and performance optimizations>",
|
|
|
|
|
|
|
| 119 |
"closingRoast": "<A final dramatic one-liner, Gordon Ramsay style>",
|
| 120 |
"wouldHire": "<YES|MAYBE|GET OUT OF MY KITCHEN>"
|
| 121 |
}
|
| 122 |
|
| 123 |
+
IMPORTANT: Return ONLY the JSON object. No markdown formatting, no backticks, no explanation outside the JSON.`;
|
| 124 |
+
const text = await callAI(prompt);
|
| 125 |
return parseJSON(text);
|
| 126 |
}
|
| 127 |
|
frontend/src/App.jsx
CHANGED
|
@@ -3,19 +3,64 @@ import { roastCode } from './utils/api';
|
|
| 3 |
|
| 4 |
const LANGUAGES = ['Auto-detect', 'JavaScript', 'Python', 'TypeScript', 'Java', 'C++', 'Go', 'Rust', 'PHP', 'Ruby', 'CSS', 'HTML', 'SQL', 'Other'];
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
export default function App() {
|
| 7 |
const [code, setCode] = useState('');
|
| 8 |
const [language, setLanguage] = useState('Auto-detect');
|
| 9 |
const [result, setResult] = useState(null);
|
| 10 |
const [loading, setLoading] = useState(false);
|
| 11 |
const [error, setError] = useState('');
|
|
|
|
| 12 |
|
| 13 |
const handleRoast = async () => {
|
| 14 |
if (!code.trim()) { setError('Paste some code first!'); return; }
|
| 15 |
-
setLoading(true); setError('');
|
| 16 |
try {
|
| 17 |
const data = await roastCode(code, language === 'Auto-detect' ? '' : language);
|
| 18 |
setResult(data);
|
|
|
|
| 19 |
} catch (err) { setError(err.message); }
|
| 20 |
finally { setLoading(false); }
|
| 21 |
};
|
|
@@ -29,61 +74,272 @@ export default function App() {
|
|
| 29 |
|
| 30 |
if (loading) {
|
| 31 |
return (
|
| 32 |
-
<div className=
|
| 33 |
<h1><span>Code Roast Battle</span></h1>
|
| 34 |
-
<div className=
|
| 35 |
-
<div className=
|
| 36 |
-
<p>Chef CodeRamsay is
|
| 37 |
-
<p style={{ color: '#8b949e', marginTop: '0.5rem', fontSize: '0.9rem' }}>
|
| 38 |
</div>
|
| 39 |
</div>
|
| 40 |
);
|
| 41 |
}
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
if (result) {
|
| 44 |
return (
|
| 45 |
-
<div className=
|
| 46 |
<h1><span>Code Roast Battle</span></h1>
|
| 47 |
-
<div className=
|
| 48 |
-
<div className=
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
| 53 |
</div>
|
| 54 |
|
| 55 |
-
{
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
<div
|
| 60 |
-
<div
|
| 61 |
-
<div
|
| 62 |
-
<div className="fix-text">Fix: {issue.fix}</div>
|
| 63 |
</div>
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
</div>
|
| 66 |
)}
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
<
|
| 74 |
-
{
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
{
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
</div>
|
| 84 |
)}
|
| 85 |
|
| 86 |
-
<div className="closing-roast"
|
| 87 |
<button className="btn back-btn" onClick={() => setResult(null)}>Roast More Code</button>
|
| 88 |
</div>
|
| 89 |
</div>
|
|
@@ -93,15 +349,15 @@ export default function App() {
|
|
| 93 |
return (
|
| 94 |
<div className="app">
|
| 95 |
<h1><span>Code Roast Battle</span></h1>
|
| 96 |
-
<p className="subtitle"
|
| 97 |
-
{error && <p style={{ color:
|
| 98 |
<div className="editor-section">
|
| 99 |
<label>Language</label>
|
| 100 |
<select value={language} onChange={e => setLanguage(e.target.value)}>
|
| 101 |
{LANGUAGES.map(l => <option key={l}>{l}</option>)}
|
| 102 |
</select>
|
| 103 |
<label>Paste your code (if you dare)</label>
|
| 104 |
-
<textarea className="code-input" placeholder="// Paste your code here..." value={code} onChange={e => setCode(e.target.value)} onKeyDown={e => { if (e.key ===
|
| 105 |
</div>
|
| 106 |
<button className="btn" onClick={handleRoast}>ROAST MY CODE</button>
|
| 107 |
</div>
|
|
|
|
| 3 |
|
| 4 |
const LANGUAGES = ['Auto-detect', 'JavaScript', 'Python', 'TypeScript', 'Java', 'C++', 'Go', 'Rust', 'PHP', 'Ruby', 'CSS', 'HTML', 'SQL', 'Other'];
|
| 5 |
|
| 6 |
+
function ScoreBar({ label, value, max = 100 }) {
|
| 7 |
+
const pct = Math.round((Number(value) / max) * 100);
|
| 8 |
+
const barColor = pct >= 80 ? '#238636' : pct >= 50 ? '#9e6a03' : '#da3633';
|
| 9 |
+
return (
|
| 10 |
+
<div style={{ marginBottom: '0.5rem' }}>
|
| 11 |
+
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem', marginBottom: '0.2rem' }}>
|
| 12 |
+
<span style={{ color: '#8b949e' }}>{label}</span>
|
| 13 |
+
<span style={{ color: barColor, fontWeight: 700 }}>{value}/{max}</span>
|
| 14 |
+
</div>
|
| 15 |
+
<div style={{ background: '#21262d', borderRadius: 6, height: 8, overflow: 'hidden' }}>
|
| 16 |
+
<div style={{ width: pct + '%', background: barColor, height: '100%', borderRadius: 6, transition: 'width 0.5s' }} />
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
function SeverityBadge({ level }) {
|
| 23 |
+
const colors = {
|
| 24 |
+
critical: '#da3633', high: '#da3633', nuclear: '#da3633',
|
| 25 |
+
medium: '#9e6a03', warning: '#9e6a03', spicy: '#9e6a03',
|
| 26 |
+
low: '#238636', info: '#388bfd', mild: '#238636'
|
| 27 |
+
};
|
| 28 |
+
const bg = colors[level] || '#30363d';
|
| 29 |
+
return <span style={{ display: 'inline-block', padding: '0.1rem 0.5rem', borderRadius: 10, fontSize: '0.75rem', fontWeight: 700, background: bg, color: '#fff' }}>{level}</span>;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
function GradeDisplay({ grade }) {
|
| 33 |
+
const gradeColors = {
|
| 34 |
+
'A+': '#238636', 'A': '#238636', 'A-': '#2ea043',
|
| 35 |
+
'B+': '#56d364', 'B': '#9e6a03', 'B-': '#9e6a03',
|
| 36 |
+
'C+': '#d29922', 'C': '#d29922', 'C-': '#e3b341',
|
| 37 |
+
'D+': '#da3633', 'D': '#da3633', 'D-': '#da3633',
|
| 38 |
+
'F': '#da3633'
|
| 39 |
+
};
|
| 40 |
+
const color = gradeColors[grade] || '#8b949e';
|
| 41 |
+
return (
|
| 42 |
+
<div style={{ textAlign: 'center' }}>
|
| 43 |
+
<div style={{ fontSize: '3.5rem', fontWeight: 900, color, lineHeight: 1 }}>{grade}</div>
|
| 44 |
+
<div style={{ color: '#8b949e', fontSize: '0.8rem', marginTop: '0.3rem' }}>Letter Grade</div>
|
| 45 |
+
</div>
|
| 46 |
+
);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
export default function App() {
|
| 50 |
const [code, setCode] = useState('');
|
| 51 |
const [language, setLanguage] = useState('Auto-detect');
|
| 52 |
const [result, setResult] = useState(null);
|
| 53 |
const [loading, setLoading] = useState(false);
|
| 54 |
const [error, setError] = useState('');
|
| 55 |
+
const [activeTab, setActiveTab] = useState('overview');
|
| 56 |
|
| 57 |
const handleRoast = async () => {
|
| 58 |
if (!code.trim()) { setError('Paste some code first!'); return; }
|
| 59 |
+
setLoading(true); setError(''); setResult(null);
|
| 60 |
try {
|
| 61 |
const data = await roastCode(code, language === 'Auto-detect' ? '' : language);
|
| 62 |
setResult(data);
|
| 63 |
+
setActiveTab('overview');
|
| 64 |
} catch (err) { setError(err.message); }
|
| 65 |
finally { setLoading(false); }
|
| 66 |
};
|
|
|
|
| 74 |
|
| 75 |
if (loading) {
|
| 76 |
return (
|
| 77 |
+
<div className='app'>
|
| 78 |
<h1><span>Code Roast Battle</span></h1>
|
| 79 |
+
<div className='loading'>
|
| 80 |
+
<div className='spinner'></div>
|
| 81 |
+
<p>Chef CodeRamsay is performing deep analysis...</p>
|
| 82 |
+
<p style={{ color: '#8b949e', marginTop: '0.5rem', fontSize: '0.9rem' }}>Security scan, performance review, clean code audit...</p>
|
| 83 |
</div>
|
| 84 |
</div>
|
| 85 |
);
|
| 86 |
}
|
| 87 |
|
| 88 |
+
const tabs = [
|
| 89 |
+
{ id: 'overview', label: 'Overview' },
|
| 90 |
+
{ id: 'security', label: 'Security' },
|
| 91 |
+
{ id: 'performance', label: 'Performance' },
|
| 92 |
+
{ id: 'cleancode', label: 'Clean Code' },
|
| 93 |
+
{ id: 'issues', label: 'Issues' },
|
| 94 |
+
{ id: 'tests', label: 'Tests' },
|
| 95 |
+
{ id: 'rewrite', label: 'Rewrite' },
|
| 96 |
+
];
|
| 97 |
+
|
| 98 |
if (result) {
|
| 99 |
return (
|
| 100 |
+
<div className='app'>
|
| 101 |
<h1><span>Code Roast Battle</span></h1>
|
| 102 |
+
<div className='results'>
|
| 103 |
+
<div className='opening-roast'>“{result.openingRoast}”</div>
|
| 104 |
+
|
| 105 |
+
{/* Tab Navigation */}
|
| 106 |
+
<div className='tab-nav'>
|
| 107 |
+
{tabs.map(t => (
|
| 108 |
+
<button key={t.id} className={'tab-btn' + (activeTab === t.id ? ' active' : '')} onClick={() => setActiveTab(t.id)}>{t.label}</button>
|
| 109 |
+
))}
|
| 110 |
</div>
|
| 111 |
|
| 112 |
+
{/* OVERVIEW TAB */}
|
| 113 |
+
{activeTab === 'overview' && (
|
| 114 |
+
<div>
|
| 115 |
+
<div className='score-grid'>
|
| 116 |
+
<div className='score-card'>
|
| 117 |
+
<div className='score-big'>{result.overallScore}<span style={{fontSize:'1.2rem'}}>/100</span></div>
|
| 118 |
+
<div style={{textAlign:'center',color:'#8b949e',fontSize:'0.85rem'}}>Overall Score</div>
|
|
|
|
| 119 |
</div>
|
| 120 |
+
{result.letterGrade && <div className='score-card'><GradeDisplay grade={result.letterGrade} /></div>}
|
| 121 |
+
<div className='score-card'>
|
| 122 |
+
<div className={'hire-badge ' + getHireClass(result.wouldHire)}>{result.wouldHire}</div>
|
| 123 |
+
<div style={{color:'#8b949e',fontSize:'0.8rem',marginTop:'0.5rem'}}>{result.roastLevel}</div>
|
| 124 |
+
</div>
|
| 125 |
+
</div>
|
| 126 |
+
|
| 127 |
+
{/* Score Overview Bars */}
|
| 128 |
+
<div className='roast-card'>
|
| 129 |
+
<div className='section-title'>Score Breakdown</div>
|
| 130 |
+
{result.securityAnalysis && <ScoreBar label='Security' value={result.securityAnalysis.score} />}
|
| 131 |
+
{result.performanceAnalysis && <ScoreBar label='Performance' value={result.performanceAnalysis.score} />}
|
| 132 |
+
{result.bestPractices && <ScoreBar label='Best Practices' value={result.bestPractices.score} />}
|
| 133 |
+
{result.cleanCodeScore != null && <ScoreBar label='Clean Code' value={result.cleanCodeScore} />}
|
| 134 |
+
{result.maintainabilityIndex != null && <ScoreBar label='Maintainability' value={result.maintainabilityIndex} />}
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
{/* Dependency Analysis */}
|
| 138 |
+
{result.dependencyAnalysis && (
|
| 139 |
+
<div className='roast-card'>
|
| 140 |
+
<div className='section-title'>Dependency / Import Analysis</div>
|
| 141 |
+
{result.dependencyAnalysis.unusedImports?.length > 0 && (
|
| 142 |
+
<div style={{marginBottom:'0.8rem'}}>
|
| 143 |
+
<div style={{color:'#d29922',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Unused Imports</div>
|
| 144 |
+
{result.dependencyAnalysis.unusedImports.map((u, i) => <span key={i} className='tag tag-warn'>{u}</span>)}
|
| 145 |
+
</div>
|
| 146 |
+
)}
|
| 147 |
+
{result.dependencyAnalysis.missingImports?.length > 0 && (
|
| 148 |
+
<div style={{marginBottom:'0.8rem'}}>
|
| 149 |
+
<div style={{color:'#da3633',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Missing Imports</div>
|
| 150 |
+
{result.dependencyAnalysis.missingImports.map((m, i) => <span key={i} className='tag tag-error'>{m}</span>)}
|
| 151 |
+
</div>
|
| 152 |
+
)}
|
| 153 |
+
{result.dependencyAnalysis.suggestions?.length > 0 && (
|
| 154 |
+
<div>
|
| 155 |
+
<div style={{color:'#388bfd',fontSize:'0.9rem',marginBottom:'0.3rem'}}>Suggestions</div>
|
| 156 |
+
{result.dependencyAnalysis.suggestions.map((s, i) => <div key={i} style={{color:'#8b949e',fontSize:'0.85rem',marginBottom:'0.3rem'}}>- {s}</div>)}
|
| 157 |
+
</div>
|
| 158 |
+
)}
|
| 159 |
+
</div>
|
| 160 |
+
)}
|
| 161 |
</div>
|
| 162 |
)}
|
| 163 |
|
| 164 |
+
{/* SECURITY TAB */}
|
| 165 |
+
{activeTab === 'security' && result.securityAnalysis && (
|
| 166 |
+
<div>
|
| 167 |
+
<div className='roast-card'>
|
| 168 |
+
<div className='section-title'>Security Analysis</div>
|
| 169 |
+
<ScoreBar label='Security Score' value={result.securityAnalysis.score} />
|
| 170 |
+
<p style={{color:'#8b949e',fontSize:'0.9rem',marginTop:'0.5rem',marginBottom:'1rem'}}>{result.securityAnalysis.summary}</p>
|
| 171 |
+
{result.securityAnalysis.vulnerabilities?.map((v, i) => (
|
| 172 |
+
<div key={i} className='vuln-card'>
|
| 173 |
+
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:'0.5rem'}}>
|
| 174 |
+
<span style={{color:'#f97316',fontWeight:700}}>{v.type}</span>
|
| 175 |
+
<SeverityBadge level={v.severity} />
|
| 176 |
+
</div>
|
| 177 |
+
<div style={{color:'#c9d1d9',fontSize:'0.9rem',marginBottom:'0.3rem'}}>{v.description}</div>
|
| 178 |
+
{v.location && <div style={{color:'#8b949e',fontSize:'0.8rem',marginBottom:'0.3rem'}}>Location: <code>{v.location}</code></div>}
|
| 179 |
+
<div style={{color:'#7ee787',fontSize:'0.85rem',marginBottom:'0.3rem'}}>Fix: {v.fix}</div>
|
| 180 |
+
{v.roast && <div style={{color:'#f97316',fontStyle:'italic',fontSize:'0.85rem'}}>{v.roast}</div>}
|
| 181 |
+
</div>
|
| 182 |
+
))}
|
| 183 |
+
{(!result.securityAnalysis.vulnerabilities || result.securityAnalysis.vulnerabilities.length === 0) && (
|
| 184 |
+
<div style={{color:'#238636',textAlign:'center',padding:'1rem'}}>No security vulnerabilities detected. Chef is impressed... for once.</div>
|
| 185 |
+
)}
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
)}
|
| 189 |
+
|
| 190 |
+
{/* PERFORMANCE TAB */}
|
| 191 |
+
{activeTab === 'performance' && result.performanceAnalysis && (
|
| 192 |
+
<div>
|
| 193 |
+
<div className='roast-card'>
|
| 194 |
+
<div className='section-title'>Performance Analysis</div>
|
| 195 |
+
<ScoreBar label='Performance Score' value={result.performanceAnalysis.score} />
|
| 196 |
+
<div style={{display:'flex',gap:'1.5rem',margin:'1rem 0',flexWrap:'wrap'}}>
|
| 197 |
+
{result.performanceAnalysis.timeComplexity && (
|
| 198 |
+
<div className='complexity-badge'>
|
| 199 |
+
<div style={{color:'#8b949e',fontSize:'0.75rem'}}>Time</div>
|
| 200 |
+
<div style={{color:'#f97316',fontWeight:700,fontSize:'1.1rem'}}>{result.performanceAnalysis.timeComplexity}</div>
|
| 201 |
+
</div>
|
| 202 |
+
)}
|
| 203 |
+
{result.performanceAnalysis.spaceComplexity && (
|
| 204 |
+
<div className='complexity-badge'>
|
| 205 |
+
<div style={{color:'#8b949e',fontSize:'0.75rem'}}>Space</div>
|
| 206 |
+
<div style={{color:'#388bfd',fontWeight:700,fontSize:'1.1rem'}}>{result.performanceAnalysis.spaceComplexity}</div>
|
| 207 |
+
</div>
|
| 208 |
+
)}
|
| 209 |
+
</div>
|
| 210 |
+
<p style={{color:'#8b949e',fontSize:'0.9rem',marginBottom:'1rem'}}>{result.performanceAnalysis.summary}</p>
|
| 211 |
+
{result.performanceAnalysis.issues?.map((issue, i) => (
|
| 212 |
+
<div key={i} className='issue-card'>
|
| 213 |
+
<div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}>
|
| 214 |
+
<span style={{color:'#c9d1d9',fontWeight:600}}>{issue.issue}</span>
|
| 215 |
+
<SeverityBadge level={issue.impact} />
|
| 216 |
+
</div>
|
| 217 |
+
<div style={{color:'#7ee787',fontSize:'0.85rem'}}>Suggestion: {issue.suggestion}</div>
|
| 218 |
+
</div>
|
| 219 |
+
))}
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
)}
|
| 223 |
+
|
| 224 |
+
{/* CLEAN CODE TAB */}
|
| 225 |
+
{activeTab === 'cleancode' && (
|
| 226 |
+
<div>
|
| 227 |
+
{result.cleanCodeBreakdown && (
|
| 228 |
+
<div className='roast-card'>
|
| 229 |
+
<div className='section-title'>Clean Code Breakdown (Robert C. Martin)</div>
|
| 230 |
+
<ScoreBar label='Overall Clean Code Score' value={result.cleanCodeScore} />
|
| 231 |
+
<div style={{marginTop:'1rem'}}>
|
| 232 |
+
<ScoreBar label='Meaningful Names' value={result.cleanCodeBreakdown.meaningfulNames} max={10} />
|
| 233 |
+
<ScoreBar label='Small Functions' value={result.cleanCodeBreakdown.smallFunctions} max={10} />
|
| 234 |
+
<ScoreBar label='Single Responsibility' value={result.cleanCodeBreakdown.singleResponsibility} max={10} />
|
| 235 |
+
<ScoreBar label='DRY Principle' value={result.cleanCodeBreakdown.dryPrinciple} max={10} />
|
| 236 |
+
<ScoreBar label='Error Handling' value={result.cleanCodeBreakdown.errorHandling} max={10} />
|
| 237 |
+
<ScoreBar label='Readability' value={result.cleanCodeBreakdown.readability} max={10} />
|
| 238 |
+
<ScoreBar label='Formatting' value={result.cleanCodeBreakdown.formatting} max={10} />
|
| 239 |
+
<ScoreBar label='Comments' value={result.cleanCodeBreakdown.comments} max={10} />
|
| 240 |
+
<ScoreBar label='No Side Effects' value={result.cleanCodeBreakdown.noSideEffects} max={10} />
|
| 241 |
+
<ScoreBar label='Testability' value={result.cleanCodeBreakdown.testability} max={10} />
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
)}
|
| 245 |
+
|
| 246 |
+
{result.maintainabilityIndex != null && (
|
| 247 |
+
<div className='roast-card'>
|
| 248 |
+
<div className='section-title'>Maintainability Index</div>
|
| 249 |
+
<ScoreBar label='Maintainability' value={result.maintainabilityIndex} />
|
| 250 |
+
</div>
|
| 251 |
+
)}
|
| 252 |
+
|
| 253 |
+
{/* Best Practices */}
|
| 254 |
+
{result.bestPractices?.violations?.length > 0 && (
|
| 255 |
+
<div className='roast-card'>
|
| 256 |
+
<div className='section-title'>Best Practice Violations</div>
|
| 257 |
+
{result.bestPractices.violations.map((v, i) => (
|
| 258 |
+
<div key={i} className='issue-card'>
|
| 259 |
+
<div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}>
|
| 260 |
+
<span style={{color:'#c9d1d9',fontWeight:600}}>{v.practice}</span>
|
| 261 |
+
<SeverityBadge level={v.severity} />
|
| 262 |
+
</div>
|
| 263 |
+
<div style={{color:'#8b949e',fontSize:'0.85rem',marginBottom:'0.3rem'}}>{v.description}</div>
|
| 264 |
+
<div style={{color:'#7ee787',fontSize:'0.85rem'}}>Fix: {v.fix}</div>
|
| 265 |
+
</div>
|
| 266 |
+
))}
|
| 267 |
+
</div>
|
| 268 |
+
)}
|
| 269 |
+
|
| 270 |
+
{/* Code Smells */}
|
| 271 |
+
{result.codeSmells?.length > 0 && (
|
| 272 |
+
<div className='roast-card'>
|
| 273 |
+
<div className='section-title'>Code Smells</div>
|
| 274 |
+
{result.codeSmells.map((s, i) => (
|
| 275 |
+
<div key={i} className='issue-card'>
|
| 276 |
+
<div style={{display:'flex',justifyContent:'space-between',marginBottom:'0.3rem'}}>
|
| 277 |
+
<span style={{color:'#f97316',fontWeight:600}}>{typeof s === 'string' ? s : s.smell}</span>
|
| 278 |
+
{s.severity && <SeverityBadge level={s.severity} />}
|
| 279 |
+
</div>
|
| 280 |
+
{s.location && <div style={{color:'#8b949e',fontSize:'0.8rem'}}>Location: {s.location}</div>}
|
| 281 |
+
{s.description && <div style={{color:'#8b949e',fontSize:'0.85rem',marginTop:'0.2rem'}}>{s.description}</div>}
|
| 282 |
+
</div>
|
| 283 |
+
))}
|
| 284 |
+
</div>
|
| 285 |
+
)}
|
| 286 |
+
</div>
|
| 287 |
+
)}
|
| 288 |
+
|
| 289 |
+
{/* ISSUES TAB */}
|
| 290 |
+
{activeTab === "issues" && (
|
| 291 |
+
<div>
|
| 292 |
+
{result.issues?.length > 0 && (
|
| 293 |
+
<div className="roast-card">
|
| 294 |
+
<div className="section-title">Issues Found</div>
|
| 295 |
+
{result.issues.map((issue, i) => (
|
| 296 |
+
<div key={i} className="issue-card">
|
| 297 |
+
<div style={{display:"flex",justifyContent:"space-between",marginBottom:"0.5rem"}}>
|
| 298 |
+
<code style={{color:"#8b949e"}}>{issue.line}</code>
|
| 299 |
+
<SeverityBadge level={issue.severity} />
|
| 300 |
+
</div>
|
| 301 |
+
<div className="roast-text">“{issue.roast}“</div>
|
| 302 |
+
<div className="fix-text">Fix: {issue.fix}</div>
|
| 303 |
+
</div>
|
| 304 |
+
))}
|
| 305 |
+
</div>
|
| 306 |
+
)}
|
| 307 |
+
</div>
|
| 308 |
+
)}
|
| 309 |
+
|
| 310 |
+
{/* TESTS TAB */}
|
| 311 |
+
{activeTab === "tests" && (
|
| 312 |
+
<div>
|
| 313 |
+
{result.testSuggestions?.length > 0 && (
|
| 314 |
+
<div className="roast-card">
|
| 315 |
+
<div className="section-title">Suggested Tests</div>
|
| 316 |
+
{result.testSuggestions.map((t, i) => (
|
| 317 |
+
<div key={i} className="test-card">
|
| 318 |
+
<div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"0.3rem"}}>
|
| 319 |
+
<span className={"test-type test-" + t.type}>{t.type}</span>
|
| 320 |
+
<SeverityBadge level={t.priority} />
|
| 321 |
+
</div>
|
| 322 |
+
<div style={{color:"#c9d1d9",fontSize:"0.9rem"}}>{t.description}</div>
|
| 323 |
+
</div>
|
| 324 |
+
))}
|
| 325 |
+
</div>
|
| 326 |
+
)}
|
| 327 |
+
</div>
|
| 328 |
+
)}
|
| 329 |
|
| 330 |
+
{/* REWRITE TAB */}
|
| 331 |
+
{activeTab === "rewrite" && (
|
| 332 |
+
<div>
|
| 333 |
+
{result.rewrittenCode && (
|
| 334 |
+
<div className="roast-card">
|
| 335 |
+
<div className="section-title">How It SHOULD Look</div>
|
| 336 |
+
<pre className="rewritten-code">{result.rewrittenCode}</pre>
|
| 337 |
+
</div>
|
| 338 |
+
)}
|
| 339 |
</div>
|
| 340 |
)}
|
| 341 |
|
| 342 |
+
<div className="closing-roast">“{result.closingRoast}“</div>
|
| 343 |
<button className="btn back-btn" onClick={() => setResult(null)}>Roast More Code</button>
|
| 344 |
</div>
|
| 345 |
</div>
|
|
|
|
| 349 |
return (
|
| 350 |
<div className="app">
|
| 351 |
<h1><span>Code Roast Battle</span></h1>
|
| 352 |
+
<p className="subtitle">“This code is so bad, even the compiler is crying!“ - Chef CodeRamsay</p>
|
| 353 |
+
{error && <p style={{ color: "#ef4444", textAlign: "center", marginBottom: "1rem" }}>{error}</p>}
|
| 354 |
<div className="editor-section">
|
| 355 |
<label>Language</label>
|
| 356 |
<select value={language} onChange={e => setLanguage(e.target.value)}>
|
| 357 |
{LANGUAGES.map(l => <option key={l}>{l}</option>)}
|
| 358 |
</select>
|
| 359 |
<label>Paste your code (if you dare)</label>
|
| 360 |
+
<textarea className="code-input" placeholder="// Paste your code here..." value={code} onChange={e => setCode(e.target.value)} onKeyDown={e => { if (e.key === "Tab") { e.preventDefault(); const s = e.target.selectionStart; setCode(code.substring(0, s) + " " + code.substring(e.target.selectionEnd)); setTimeout(() => { e.target.selectionStart = e.target.selectionEnd = s + 2; }, 0); }}} />
|
| 361 |
</div>
|
| 362 |
<button className="btn" onClick={handleRoast}>ROAST MY CODE</button>
|
| 363 |
</div>
|
frontend/src/styles/global.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 2 |
-
body { font-family:
|
| 3 |
.app { max-width: 900px; margin: 0 auto; padding: 2rem 1rem; }
|
| 4 |
h1 { text-align: center; font-size: 2.5rem; margin-bottom: 0.5rem; }
|
| 5 |
h1 span { background: linear-gradient(135deg, #f97316, #ef4444); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
@@ -7,7 +7,7 @@ h1 span { background: linear-gradient(135deg, #f97316, #ef4444); -webkit-backgro
|
|
| 7 |
.editor-section { background: #161b22; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid #30363d; }
|
| 8 |
.editor-section label { display: block; margin-bottom: 0.5rem; color: #8b949e; }
|
| 9 |
.editor-section select { padding: 0.5rem; border-radius: 6px; border: 1px solid #30363d; background: #0d1117; color: #c9d1d9; margin-bottom: 1rem; }
|
| 10 |
-
.code-input { width: 100%; min-height: 200px; padding: 1rem; border: 1px solid #30363d; border-radius: 8px; background: #0d1117; color: #79c0ff; font-family:
|
| 11 |
.btn { width: 100%; padding: 1rem; border: none; border-radius: 8px; font-size: 1.1rem; font-weight: 700; cursor: pointer; background: linear-gradient(135deg, #f97316, #ef4444); color: #fff; font-family: inherit; transition: transform 0.2s; }
|
| 12 |
.btn:hover { transform: scale(1.02); }
|
| 13 |
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
@@ -15,24 +15,59 @@ h1 span { background: linear-gradient(135deg, #f97316, #ef4444); -webkit-backgro
|
|
| 15 |
.loading .spinner { width: 50px; height: 50px; border: 4px solid #30363d; border-top-color: #f97316; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1rem; }
|
| 16 |
@keyframes spin { to { transform: rotate(360deg); } }
|
| 17 |
.results { margin-top: 2rem; }
|
| 18 |
-
.roast-card { background: #161b22; border-radius: 12px; padding:
|
| 19 |
.opening-roast { font-size: 1.2rem; color: #f97316; font-style: italic; text-align: center; padding: 1.5rem; background: linear-gradient(135deg, #161b22, #1c1206); border-radius: 12px; border: 1px solid #f97316; margin-bottom: 1.5rem; }
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
.hire-badge.yes { background: #238636; color: #fff; }
|
| 24 |
.hire-badge.maybe { background: #9e6a03; color: #fff; }
|
| 25 |
.hire-badge.no { background: #da3633; color: #fff; }
|
|
|
|
|
|
|
| 26 |
.issue-card { background: #0d1117; border-radius: 8px; padding: 1rem; margin-bottom: 0.8rem; border-left: 3px solid #f97316; }
|
| 27 |
.issue-card .roast-text { color: #f97316; font-style: italic; margin-bottom: 0.5rem; }
|
| 28 |
.issue-card .fix-text { color: #7ee787; }
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
.
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
.tag { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 12px; background: #30363d; color: #8b949e; font-size: 0.8rem; margin: 0.2rem; }
|
| 36 |
-
.
|
| 37 |
-
.
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 2 |
+
body { font-family: "Courier New", monospace; background: #0d1117; color: #c9d1d9; min-height: 100vh; }
|
| 3 |
.app { max-width: 900px; margin: 0 auto; padding: 2rem 1rem; }
|
| 4 |
h1 { text-align: center; font-size: 2.5rem; margin-bottom: 0.5rem; }
|
| 5 |
h1 span { background: linear-gradient(135deg, #f97316, #ef4444); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
|
|
| 7 |
.editor-section { background: #161b22; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid #30363d; }
|
| 8 |
.editor-section label { display: block; margin-bottom: 0.5rem; color: #8b949e; }
|
| 9 |
.editor-section select { padding: 0.5rem; border-radius: 6px; border: 1px solid #30363d; background: #0d1117; color: #c9d1d9; margin-bottom: 1rem; }
|
| 10 |
+
.code-input { width: 100%; min-height: 200px; padding: 1rem; border: 1px solid #30363d; border-radius: 8px; background: #0d1117; color: #79c0ff; font-family: "Courier New", monospace; font-size: 0.9rem; resize: vertical; tab-size: 2; }
|
| 11 |
.btn { width: 100%; padding: 1rem; border: none; border-radius: 8px; font-size: 1.1rem; font-weight: 700; cursor: pointer; background: linear-gradient(135deg, #f97316, #ef4444); color: #fff; font-family: inherit; transition: transform 0.2s; }
|
| 12 |
.btn:hover { transform: scale(1.02); }
|
| 13 |
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
|
|
| 15 |
.loading .spinner { width: 50px; height: 50px; border: 4px solid #30363d; border-top-color: #f97316; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1rem; }
|
| 16 |
@keyframes spin { to { transform: rotate(360deg); } }
|
| 17 |
.results { margin-top: 2rem; }
|
| 18 |
+
.roast-card { background: #161b22; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border: 1px solid #30363d; }
|
| 19 |
.opening-roast { font-size: 1.2rem; color: #f97316; font-style: italic; text-align: center; padding: 1.5rem; background: linear-gradient(135deg, #161b22, #1c1206); border-radius: 12px; border: 1px solid #f97316; margin-bottom: 1.5rem; }
|
| 20 |
+
|
| 21 |
+
/* Tab Navigation */
|
| 22 |
+
.tab-nav { display: flex; gap: 0.3rem; margin-bottom: 1.5rem; overflow-x: auto; padding-bottom: 0.5rem; border-bottom: 1px solid #30363d; }
|
| 23 |
+
.tab-btn { padding: 0.5rem 1rem; border: 1px solid transparent; border-bottom: none; border-radius: 8px 8px 0 0; background: transparent; color: #8b949e; font-family: inherit; font-size: 0.85rem; cursor: pointer; white-space: nowrap; transition: all 0.2s; }
|
| 24 |
+
.tab-btn:hover { color: #c9d1d9; background: #161b22; }
|
| 25 |
+
.tab-btn.active { color: #f97316; background: #161b22; border-color: #30363d; border-bottom: 2px solid #f97316; font-weight: 700; }
|
| 26 |
+
|
| 27 |
+
/* Score Grid */
|
| 28 |
+
.score-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
|
| 29 |
+
.score-card { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 1.5rem; text-align: center; display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
| 30 |
+
.score-big { font-size: 3.5rem; font-weight: 700; color: #f97316; line-height: 1; }
|
| 31 |
+
.hire-badge { display: inline-block; padding: 0.4rem 1.2rem; border-radius: 20px; font-weight: 700; font-size: 0.9rem; }
|
| 32 |
.hire-badge.yes { background: #238636; color: #fff; }
|
| 33 |
.hire-badge.maybe { background: #9e6a03; color: #fff; }
|
| 34 |
.hire-badge.no { background: #da3633; color: #fff; }
|
| 35 |
+
|
| 36 |
+
/* Issue Cards */
|
| 37 |
.issue-card { background: #0d1117; border-radius: 8px; padding: 1rem; margin-bottom: 0.8rem; border-left: 3px solid #f97316; }
|
| 38 |
.issue-card .roast-text { color: #f97316; font-style: italic; margin-bottom: 0.5rem; }
|
| 39 |
.issue-card .fix-text { color: #7ee787; }
|
| 40 |
+
|
| 41 |
+
/* Vulnerability Cards */
|
| 42 |
+
.vuln-card { background: #0d1117; border-radius: 8px; padding: 1rem; margin-bottom: 0.8rem; border-left: 3px solid #da3633; }
|
| 43 |
+
|
| 44 |
+
/* Complexity Badges */
|
| 45 |
+
.complexity-badge { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 0.5rem 1rem; text-align: center; }
|
| 46 |
+
|
| 47 |
+
/* Test Cards */
|
| 48 |
+
.test-card { background: #0d1117; border-radius: 8px; padding: 1rem; margin-bottom: 0.8rem; border-left: 3px solid #388bfd; }
|
| 49 |
+
.test-type { display: inline-block; padding: 0.15rem 0.6rem; border-radius: 10px; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; }
|
| 50 |
+
.test-unit { background: #238636; color: #fff; }
|
| 51 |
+
.test-integration { background: #9e6a03; color: #fff; }
|
| 52 |
+
.test-edge-case { background: #388bfd; color: #fff; }
|
| 53 |
+
|
| 54 |
+
/* Tags */
|
| 55 |
.tag { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 12px; background: #30363d; color: #8b949e; font-size: 0.8rem; margin: 0.2rem; }
|
| 56 |
+
.tag-warn { background: #3d2e00; color: #d29922; border: 1px solid #9e6a03; }
|
| 57 |
+
.tag-error { background: #3d0000; color: #f85149; border: 1px solid #da3633; }
|
| 58 |
+
|
| 59 |
+
.rewritten-code { background: #0d1117; border: 1px solid #238636; border-radius: 8px; padding: 1rem; overflow-x: auto; white-space: pre-wrap; color: #7ee787; font-size: 0.85rem; max-height: 500px; overflow-y: auto; }
|
| 60 |
+
.section-title { color: #f97316; margin-bottom: 0.8rem; font-size: 1.1rem; font-weight: 700; }
|
| 61 |
+
.closing-roast { font-size: 1.1rem; color: #ef4444; font-style: italic; text-align: center; padding: 1.5rem; margin: 1.5rem 0; background: linear-gradient(135deg, #161b22, #1c0606); border-radius: 12px; border: 1px solid #ef4444; }
|
| 62 |
+
.back-btn { background: #30363d; margin-top: 1rem; }
|
| 63 |
+
.back-btn:hover { background: #484f58; }
|
| 64 |
+
|
| 65 |
+
/* Responsive */
|
| 66 |
+
@media (max-width: 768px) {
|
| 67 |
+
h1 { font-size: 1.8rem; }
|
| 68 |
+
.score-big { font-size: 2.5rem; }
|
| 69 |
+
.score-grid { grid-template-columns: 1fr; }
|
| 70 |
+
.tab-nav { gap: 0.2rem; }
|
| 71 |
+
.tab-btn { padding: 0.4rem 0.6rem; font-size: 0.75rem; }
|
| 72 |
+
.roast-card { padding: 1rem; }
|
| 73 |
+
}
|