File size: 23,603 Bytes
cea6f93
4ca0eef
 
 
 
 
 
cea6f93
4ca0eef
 
 
cea6f93
4ca0eef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
import gradio as gr
import requests
import os
import logging
import re
import random
import json

# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# ======= CODE ANALYSIS CLASS ========
class CodeRoaster:
    def __init__(self):
        # Roast templates for different levels
        self.roast_templates = {
            "Mild": [
                "I see some opportunities for improvement in this code. It's not bad, but we can make it better!",
                "This code would benefit from a few tweaks. Let me show you how.",
                "Your code works, but there are some best practices we could apply here."
            ],
            "Medium": [
                "This code looks like it was written the night before a deadline. Time for some tough love!",
                "I've seen better code written by first-year CS students. Let's clean this up.",
                "Your code needs a serious makeover. It's like showing up to a wedding in sweatpants."
            ],
            "Savage": [
                "This code is what happens when Stack Overflow answers are copied without understanding. Pure chaos!",
                "Did you write this code during a power outage? While blindfolded? On a keyboard missing half its keys?",
                "If this code were a restaurant dish, Gordon Ramsay would throw it in the trash and shut down the kitchen."
            ]
        }
        
        # Issue templates for different languages
        self.python_issue_detectors = [
            {
                'pattern': r"range\s*\(\s*0\s*,",
                'issue': "unnecessary zero in range",
                'suggestion': "range() starts at 0 by default, so range(0, n) can be simplified to range(n)",
                'fix': lambda code: re.sub(r"range\s*\(\s*0\s*,\s*([^\)]+)\)", r"range(\1)", code)
            },
            {
                'pattern': r"print\s*\(",
                'issue': "print statements used for output",
                'suggestion': "Consider using logging for better control in production environments",
                'fix': lambda code: code  # No automatic fix
            },
            {
                'pattern': r"if\s+\*\*name\*\*\s+==",
                'issue': "incorrect __name__ syntax",
                'suggestion': 'Use if __name__ == "__main__": instead of if **name** == "__main__":',
                'fix': lambda code: re.sub(r"if\s+\*\*name\*\*\s+==", r"if __name__ ==", code)
            },
            {
                'pattern': r"def\s+\w+\s*\([^\)]*\)\s*:\s*(?!\n\s*[\"\'])",
                'issue': "missing docstring",
                'suggestion': "Add docstrings to functions explaining purpose, parameters and return values",
                'fix': lambda code: code  # No automatic fix, requires understanding function purpose
            },
            {
                'pattern': r"  [^\n]+",
                'issue': "inconsistent indentation",
                'suggestion': "Use consistent indentation (4 spaces per level is the Python standard)",
                'fix': lambda code: re.sub(r"  (?! {2})", r"    ", code)
            },
            {
                'pattern': r"except:",
                'issue': "bare except clause",
                'suggestion': "Specify exceptions to catch instead of using a bare except clause",
                'fix': lambda code: re.sub(r"except:", r"except Exception:", code)
            },
            {
                'pattern': r"from\s+\w+\s+import\s+\*",
                'issue': "wildcard import",
                'suggestion': "Import specific modules or functions instead of using wildcard imports",
                'fix': lambda code: code  # No automatic fix, requires knowledge of what's needed
            },
            {
                'pattern': r"i\s*\+=\s*1",
                'issue': "manual index increment in loop",
                'suggestion': "Consider using enumerate() for clean iteration with indices",
                'fix': lambda code: code  # No automatic fix, requires restructuring
            },
            {
                'pattern': r"with\s+open\([^,]+\)\s+as",
                'issue': "missing file mode in open()",
                'suggestion': "Specify the file mode in open(filename, mode)",
                'fix': lambda code: re.sub(r"with\s+open\(([^,]+)\)\s+as", r"with open(\1, 'r') as", code)
            }
        ]
        
        self.js_issue_detectors = [
            {
                'pattern': r"var\s+",
                'issue': "using var instead of let/const",
                'suggestion': "Use let for variables that change and const for variables that don't change",
                'fix': lambda code: re.sub(r"var\s+", r"let ", code)  # Simple replacement, not always correct
            },
            {
                'pattern': r"==(?!=)",
                'issue': "loose equality operators",
                'suggestion': "Use === instead of == for strict equality checking",
                'fix': lambda code: re.sub(r"==(?!=)", r"===", code)
            },
            {
                'pattern': r"console\.log\(",
                'issue': "console.log statements left in code",
                'suggestion': "Remove console.log statements before production deployment",
                'fix': lambda code: code  # No automatic fix
            },
            {
                'pattern': r"document\.write\(",
                'issue': "using document.write",
                'suggestion': "Avoid document.write as it can overwrite the entire document",
                'fix': lambda code: code  # No automatic fix
            },
            {
                'pattern': r"setTimeout\(\s*function\s*\(\)\s*{\s*},\s*0\)",
                'issue': "setTimeout with 0 delay",
                'suggestion': "Consider using requestAnimationFrame instead for browser animations",
                'fix': lambda code: code  # No automatic fix
            }
        ]
        
        self.generic_issue_detectors = [
            {
                'pattern': r"(.{80,})\n",
                'issue': "lines exceeding 80 characters",
                'suggestion': "Keep lines under 80 characters for better readability",
                'fix': lambda code: code  # No automatic fix, requires manual line splitting
            },
            {
                'pattern': r"(\/\/|\#)\s*TODO",
                'issue': "TODO comments in code",
                'suggestion': "Resolve TODO comments before considering code complete",
                'fix': lambda code: code  # No automatic fix
            },
            {
                'pattern': r"[^\w](\w{1,2})[^\w]",
                'issue': "short variable names",
                'suggestion': "Use descriptive variable names that explain their purpose",
                'fix': lambda code: code  # No automatic fix, requires understanding context
            }
        ]
    
    def detect_language(self, code):
        """Detect what programming language the code is written in"""
        # Python detection
        if "def " in code or "import " in code or "class " in code and ":" in code:
            return "Python"
        # JavaScript detection
        elif "function " in code or "var " in code or "let " in code or "const " in code:
            return "JavaScript"
        # Unknown language
        else:
            return "unknown"
    
    def analyze_code(self, code, language):
        """Analyze code for issues based on detected language"""
        issues = []
        suggestions = []
        fix_functions = []
        
        # Select appropriate detectors based on language
        if language == "Python":
            detectors = self.python_issue_detectors + self.generic_issue_detectors
        elif language == "JavaScript":
            detectors = self.js_issue_detectors + self.generic_issue_detectors
        else:
            detectors = self.generic_issue_detectors
        
        # Apply each detector
        for detector in detectors:
            if re.search(detector['pattern'], code):
                issues.append(detector['issue'])
                suggestions.append(detector['suggestion'])
                fix_functions.append(detector['fix'])
        
        # Check for missing newline at end of file
        if code.count('\n') > 3 and not code.strip().endswith('\n'):
            issues.append("missing newline at end of file")
            suggestions.append("Add a newline at the end of the file (standard best practice)")
            fix_functions.append(lambda code: code + '\n')
        
        return issues, suggestions, fix_functions
    
    def generate_roast(self, issues, level):
        """Generate a roast message based on the issues found and the roast level"""
        base_roast = random.choice(self.roast_templates[level])
        
        if issues:
            if level == "Mild":
                roast = f"{base_roast}\n\nI noticed these {len(issues)} issues that could be improved: {', '.join(issues)}."
            elif level == "Medium":
                roast = f"{base_roast}\n\nSpecifically, I found {len(issues)} issues that need attention: {', '.join(issues)}."
            else:  # Savage
                roast = f"{base_roast}\n\nThis masterpiece of chaos has {len(issues)} issues: {', '.join(issues)}."
        else:
            if level == "Mild":
                roast = f"{base_roast}\n\nYour code is actually pretty clean, but there are always ways to improve."
            elif level == "Medium":
                roast = f"{base_roast}\n\nSurprisingly, your code doesn't have major issues, but let's not celebrate too early."
            else:  # Savage
                roast = f"{base_roast}\n\nI was ready to demolish this code, but it's... acceptable. Don't get used to compliments."
        
        return roast
    
    def fix_code(self, code, fix_functions):
        """Apply all fix functions to the code"""
        fixed_code = code
        for fix_func in fix_functions:
            fixed_code = fix_func(fixed_code)
        return fixed_code
    
    def generate_explanation(self, issues, suggestions, language):
        """Generate an explanation of the issues and suggestions"""
        explanation = "Here's what I found and improved:\n\n"
        
        if issues:
            for i, (issue, suggestion) in enumerate(zip(issues, suggestions)):
                explanation += f"{i+1}. **{issue.capitalize()}**: {suggestion}\n"
        else:
            explanation += "- Made minor formatting improvements for better readability\n"
        
        if language != "unknown":
            explanation += f"\nYour code appears to be written in {language}. "
        
        explanation += "\nWriting clean, consistent code helps with maintainability and reduces bugs!"
        
        return explanation
    
    def roast_code(self, code, roast_level):
        """Main function to analyze, roast and fix code"""
        if not code.strip():
            return "Please enter some code to roast.", "No code provided.", "Nothing to fix."
        
        try:
            # Detect language
            language = self.detect_language(code)
            logger.info(f"Detected language: {language}")
            
            # Analyze code
            issues, suggestions, fix_functions = self.analyze_code(code, language)
            logger.info(f"Found {len(issues)} issues")
            
            # Generate roast
            roast = self.generate_roast(issues, roast_level)
            
            # Fix code
            fixed_code = self.fix_code(code, fix_functions)
            
            # Generate explanation
            explanation = self.generate_explanation(issues, suggestions, language)
            
            return roast, fixed_code, explanation
            
        except Exception as e:
            logger.error(f"Error analyzing code: {str(e)}")
            return (
                f"Oops! Something went wrong while roasting your code: {str(e)}",
                code,
                "Error generating explanation."
            )

# ======= HUGGING FACE API INTEGRATION ========
def call_huggingface_api(code, roast_level, api_key, model_name):
    if not code.strip():
        return "Please enter some code to roast.", "No code provided.", "Nothing to fix."
    
    if not api_key.strip():
        return "Please enter a valid Hugging Face API key.", code, "API key is required."
    
    try:
        logger.info(f"Calling Hugging Face API with model: {model_name}")
        
        # Create different prompts based on roast level
        roast_level_descriptions = {
            "Mild": "gentle but humorous",
            "Medium": "moderately sarcastic and pointed",
            "Savage": "brutally honest and hilariously critical"
        }
        
        # Better prompt format with less chance of format confusion
        prompt = f"""You're a senior software engineer reviewing this code:

```
{code}
```

Respond with THREE separate sections:

1. ROAST: Give a {roast_level_descriptions[roast_level]} code review. Be humorous but point out real issues.

2. FIXED_CODE: Provide an improved version of the code.

3. EXPLANATION: Explain what was improved and why it matters.

Start each section with the heading exactly as shown above.
"""
        
        # API URL based on selected model
        API_URL = f"https://api-inference.huggingface.co/models/{model_name}"
        
        # Set up the headers with API key
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        
        # Set up the payload
        payload = {
            "inputs": prompt,
            "parameters": {
                "max_new_tokens": 2048,
                "temperature": 0.7,
                "top_p": 0.9,
                "top_k": 50,
                "repetition_penalty": 1.2,
                "stop": ["<|endoftext|>", "</s>"]
            }
        }
        
        # Make the API call
        response = requests.post(API_URL, headers=headers, json=payload)
        
        # Check if the request was successful
        if response.status_code != 200:
            logger.error(f"API Error: {response.status_code} - {response.text}")
            return f"Error: {response.status_code} - {response.text}", code, "API call failed."
        
        # Extract the response text
        try:
            response_text = response.json()[0]["generated_text"]
            logger.info(f"Raw API response received with length: {len(response_text)}")
            
            # Remove the original prompt if it's included in the response
            if prompt in response_text:
                response_text = response_text.replace(prompt, "")
            
            # Now extract each section using regex
            roast_match = re.search(r"ROAST:(.*?)(?=FIXED_CODE:|$)", response_text, re.DOTALL)
            code_match = re.search(r"FIXED_CODE:(.*?)(?=EXPLANATION:|$)", response_text, re.DOTALL)
            explanation_match = re.search(r"EXPLANATION:(.*?)$", response_text, re.DOTALL)
            
            # Extract content from matches
            roast = roast_match.group(1).strip() if roast_match else "Failed to generate roast."
            
            # For the code section, also look for code blocks
            if code_match:
                code_section = code_match.group(1).strip()
                # Try to extract code between triple backticks if present
                code_block_match = re.search(r"```(?:\w+)?\n(.*?)```", code_section, re.DOTALL)
                if code_block_match:
                    fixed_code = code_block_match.group(1).strip()
                else:
                    # If no code blocks, use the whole section
                    fixed_code = code_section
            else:
                fixed_code = "Failed to generate fixed code."
            
            explanation = explanation_match.group(1).strip() if explanation_match else "Failed to generate explanation."
            
            # If sections are still missing or empty, provide default messages
            if not roast or roast == "":
                roast = "The model didn't generate a proper roast. Try again or use local mode."
            
            if not fixed_code or fixed_code == "":
                fixed_code = code
            
            if not explanation or explanation == "":
                explanation = "The model didn't generate a proper explanation. Try again or use local mode."
            
            logger.info("Successfully parsed response from Hugging Face API")
            return roast, fixed_code, explanation
            
        except Exception as e:
            logger.error(f"Error parsing API response: {e}")
            logger.error(f"Response content: {response.content[:500]}")  # Log first 500 chars
            return "Error parsing API response", code, f"Error details: {str(e)}"
    
    except Exception as e:
        logger.error(f"Error calling Hugging Face API: {str(e)}")
        return (
            f"Oops! Something went wrong while roasting your code: {str(e)}",
            code,
            "Error generating explanation."
        )

# ======= MAIN ROASTING FUNCTION ========
def roast_code(code, roast_level, use_api, api_key="", model_choice="Mistral-7B"):
    # Map model choice to actual model name
    model_map = {
        "Mistral-7B": "mistralai/Mistral-7B-Instruct-v0.2",
        "Falcon-7B": "tiiuae/falcon-7b-instruct",
        "Llama-2-7B": "meta-llama/Llama-2-7b-chat-hf",
        "CodeLlama-7B": "codellama/CodeLlama-7b-instruct-hf"
    }
    
    model_name = model_map.get(model_choice, model_map["Mistral-7B"])
    
    if use_api and api_key.strip():
        # Use the API with specified model
        logger.info(f"Using API with model: {model_name}")
        return call_huggingface_api(code, roast_level, api_key, model_name)
    else:
        # Use the local analysis
        logger.info("Using local code analysis")
        roaster = CodeRoaster()
        return roaster.roast_code(code, roast_level)

# ======= GRADIO INTERFACE ========
def create_interface():
    with gr.Blocks(title="AI Roast My Code") as app:
        gr.Markdown("# πŸ”₯ AI Roast My Code πŸ”₯")
        gr.Markdown("Let our AI roast your code, fix it, and teach you - all while making you laugh!")
        
        with gr.Row():
            with gr.Column(scale=3):
                code_input = gr.Textbox(
                    label="Your Code",
                    lines=15
                )
            with gr.Column(scale=1):
                roast_level = gr.Radio(
                    choices=["Mild", "Medium", "Savage"],
                    label="Roast Level",
                    value="Medium"
                )
                
                use_api = gr.Checkbox(label="Use Hugging Face API", value=True)
                
                with gr.Group(visible=True) as api_settings:
                    api_key = gr.Textbox(
                        label="Hugging Face API Key", 
                        type="password",
                        value=os.environ.get("HF_API_KEY", "")
                    )
                    
                    model_choice = gr.Dropdown(
                        choices=["Mistral-7B", "Falcon-7B", "Llama-2-7B", "CodeLlama-7B"],
                        label="Model",
                        value="Mistral-7B",
                        info="Select which LLM to use for code analysis"
                    )
                
                submit_btn = gr.Button("πŸ”₯ Roast My Code!", variant="primary")
        
        # Function to toggle API settings visibility
        def toggle_api_settings(use_api):
            return gr.Group.update(visible=use_api)
        
        use_api.change(fn=toggle_api_settings, inputs=use_api, outputs=api_settings)
        
        # Output Sections
        with gr.Row():
            loading_indicator = gr.Textbox(label="Status", value="")
            
        with gr.Tab("Roast πŸ”₯"):
            roast_output = gr.Textbox(label="Roast", lines=5)
            
        with gr.Tab("Fixed Code βœ…"):
            fixed_code_output = gr.Textbox(label="Fixed Code", lines=10)
            
        with gr.Tab("Explanation πŸ“"):
            explanation_output = gr.Textbox(label="Explanation", lines=5)
        
        # Example code
        example_python_code = '''def calculate_fibonacci(n):
    # This function calculates the fibonacci sequence
    result = []
    a, b = 0, 1
    for i in range(0, n):
        result.append(a)
        a, b = b, a + b
    
    return result

if **name** == "__main__":
    print("Fibonacci Sequence:")
    print(calculate_fibonacci(10))
'''

        # Event handlers
        def load_example():
            return example_python_code
            
        def clear_inputs():
            return ""
        
        def show_loading():
            return "⏳ AI is analyzing your code and preparing a roast. This may take a few moments..."
        
        def hide_loading():
            return "βœ… Roast complete! Check the tabs below for results."
        
        # Set up buttons for examples and clearing
        with gr.Row():
            example_btn = gr.Button("Load Example Code")
            clear_btn = gr.Button("Clear")
            
        example_btn.click(fn=load_example, outputs=code_input)
        clear_btn.click(fn=clear_inputs, outputs=code_input)
        
        # Main submit button with loading state
        submit_btn.click(
            fn=show_loading,
            outputs=loading_indicator
        ).then(
            fn=roast_code,
            inputs=[code_input, roast_level, use_api, api_key, model_choice],
            outputs=[roast_output, fixed_code_output, explanation_output]
        ).then(
            fn=hide_loading,
            outputs=loading_indicator
        )
        
        # About Section
        gr.Markdown("""
        ### πŸ€– How It Works
        
        "AI Roast My Code" can work in two modes:
        
        1. **API Mode**: Uses the Hugging Face API with various models to provide advanced code analysis
           - Multiple model options including Mistral, Falcon, Llama-2, and CodeLlama
           - Requires a Hugging Face API key
        
        2. **Local Mode**: Uses built-in pattern matching for basic code analysis (no API needed)
           - Works completely offline
           - No API key required
           - Detects common issues in Python and JavaScript
        
        The app will:
        1. **Analyze** your code for problems, anti-patterns, and style issues
        2. **Roast** it with humor that ranges from gentle to savage (you choose!)
        3. **Fix** the issues and provide an improved version
        4. **Explain** what was wrong and how it was improved
        
        ### πŸ“ Note
        
        - For educational purposes only - always review generated code before using it
        """)
    
    return app

# Main function
def main():
    try:
        logger.info("Starting AI Roast My Code...")
        
        # Create the interface
        app = create_interface()
        
        # Launch the app
        logger.info("Launching Gradio interface...")
        app.launch(share=True)
    except Exception as e:
        logger.error(f"Error launching application: {str(e)}")
        raise

if __name__ == "__main__":
    main()