File size: 7,186 Bytes
2a414bf
e893bee
 
2a414bf
 
aa1b688
2a414bf
aa1b688
 
 
2a414bf
 
aa1b688
 
 
 
2a414bf
808f948
 
2a414bf
 
 
 
 
 
 
 
 
 
 
aa1b688
2a414bf
 
 
 
aa1b688
 
 
 
 
 
2a414bf
 
 
 
 
aa1b688
2a414bf
 
 
 
 
 
 
e893bee
 
 
 
 
 
 
 
 
aa1b688
 
 
e893bee
aa1b688
 
 
e893bee
2a414bf
aa1b688
e893bee
aa1b688
 
 
 
 
 
 
 
 
2a414bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa1b688
e893bee
aa1b688
e893bee
 
 
 
 
 
 
 
 
 
aa1b688
 
 
e893bee
 
2a414bf
e893bee
2a414bf
aa1b688
 
2a414bf
e893bee
2a414bf
e893bee
 
2a414bf
 
 
 
aa1b688
 
2a414bf
aa1b688
 
 
2a414bf
e893bee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a414bf
 
e893bee
 
 
 
 
2a414bf
 
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
import os
import zipfile
import tempfile
import gradio as gr
from docx import Document
from huggingface_hub import InferenceClient

# -----------------------------
# 1️⃣ Hugging Face client
# -----------------------------
HF_TOKEN = os.getenv("HUGGINGFACE_API_KEY")
client = InferenceClient(api_key=HF_TOKEN)

# -----------------------------
# 2️⃣ Review prompt
# -----------------------------
promt = """
You are a senior software engineer with 10+ years of experience reviewing code in multiple languages.
Provide feedback like a human: friendly, clear, and actionable. 
Give short, clear feedback in bullet points.
**Rules:**
Review the code and provide actionable points under these 4 areas:
1. **Code Standards** β€” Naming, formatting, magic numbers, comments
2. **Security** β€” SQL injection, input validation, hardcoded secrets, authentication issues
3. **Reusability** β€” Duplicate code, missing helper functions, not using libraries
4. **Refactoring** β€” Simplify complex code, performance improvements, better error handling
**Format:**
- Each suggestion must be a single concise line:  
  `Line X: Problem β€” Fix`
- Always **show both the incorrect and corrected examples** when suggesting naming or syntax improvements.
- Use **real corrected form** (e.g., `_AuthService` β†’ `_authService`).
- Keep tone friendly, direct, and professional.
- Do not repeat identical feedback for multiple lines; combine where possible.
"""

ignore_folders = ['.venv', 'wwwroot', 'node_modules', '__pycache__', 'bin', 'obj', 'properties']
ALLOWED_EXTS = [".py", ".js", ".java", ".cs", ".cpp", ".ts", ".cshtml", ".razor"]

# -----------------------------
# 3️⃣ Helper functions
# -----------------------------
def analyze_code_with_ai(code_text: str, filename: str) -> str:
    try:
        completion = client.chat.completions.create(
            model="meta-llama/Llama-3.1-8B-Instruct",
            messages=[
                {"role": "system", "content": promt},
                {"role": "user", "content": f"Review the following code from {filename}:\n\n{code_text}"}
            ]
        )
        return completion.choices[0].message["content"]
    except Exception as e:
        return f"⚠️ Error: {str(e)}"

def extract_zip_to_temp(zip_file_path):
    temp_dir = tempfile.mkdtemp()
    with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
        zip_ref.extractall(temp_dir)
    return temp_dir

def list_subfolders(folder_path):
    folders = [os.path.basename(folder_path)]
    for root, dirs, _ in os.walk(folder_path):
        for d in dirs:
            if any(ignored in root for ignored in ignore_folders):
                continue
            rel_path = os.path.relpath(os.path.join(root, d), folder_path)
            folders.append(rel_path)
    return folders

def process_selected_folders(base_path, selected_folders):
    reviews = {}
    for subfolder in selected_folders:
        full_path = base_path if subfolder == os.path.basename(base_path) else os.path.join(base_path, subfolder)
        for root, _, files in os.walk(full_path):
            if any(ignored in root for ignored in ignore_folders):
                continue
            for file in files:
                if any(file.endswith(ext) for ext in ALLOWED_EXTS):
                    filepath = os.path.join(root, file)
                    with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
                        code = f.read()
                    reviews[file] = analyze_code_with_ai(code, file)
    return reviews

def process_file(file_path):
    filename = os.path.basename(file_path)
    with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
        code = f.read()
    return {filename: analyze_code_with_ai(code, filename)}

def generate_report(reviews, output_path="review_report.docx"):
    doc = Document()
    doc.add_heading("Code Review Report", 0)
    for fname, review in reviews.items():
        doc.add_heading(fname, level=1)
        doc.add_paragraph(review)
    doc.save(output_path)
    return output_path

# -----------------------------
# 4️⃣ Gradio functions
# -----------------------------
# List subfolders after ZIP upload
def load_subfolders_from_zip(zip_file):
    if not zip_file:
        return gr.update(choices=[], value=[]), "⚠️ No ZIP uploaded."
    folder_path = extract_zip_to_temp(zip_file.name)
    folders = list_subfolders(folder_path)
    return gr.update(choices=folders, value=folders), folder_path, f"βœ… Found {len(folders)} folders."

# Review selected subfolders
def review_zip_selected(folder_path, selected_folders):
    if not folder_path or not os.path.isdir(folder_path):
        return "⚠️ Invalid folder path.", None
    if not selected_folders:
        return "⚠️ No folders selected.", None
    reviews = process_selected_folders(folder_path, selected_folders)
    if not reviews:
        return "⚠️ No code files found.", None
    report_path = generate_report(reviews)
    output_text = "\n\n".join([f"πŸ“Œ {f}:\n{r}" for f, r in reviews.items()])
    return output_text, report_path

# Review single file
def review_single_file(file_obj):
    if not file_obj:
        return "⚠️ No file uploaded.", None
    reviews = process_file(file_obj.name)
    if not reviews:
        return "⚠️ Could not analyze file.", None
    report_path = generate_report(reviews)
    output_text = "\n\n".join([f"πŸ“Œ {f}:\n{r}" for f, r in reviews.items()])
    return output_text, report_path

# -----------------------------
# 5️⃣ Gradio UI
# -----------------------------
with gr.Blocks() as demo:
    gr.Markdown("# πŸ€– AI Code Reviewer\nUpload a ZIP or a single file and get a code review report.")

    with gr.Tab("πŸ“¦ Review ZIP with Folder Selection"):
     zip_input = gr.File(label="Upload ZIP File", file_types=[".zip"])
     load_btn = gr.Button("πŸ“ Load Subfolders")
     folder_status = gr.Markdown("")
     folder_select = gr.CheckboxGroup(label="Select Folders to Review")
     run_zip_btn = gr.Button("πŸš€ Run Review")
     zip_output_text = gr.Textbox(label="AI Review Output", lines=15)
     zip_report_file = gr.File(label="Download DOCX Report", type="filepath")

     # Store the temp folder path in gr.State
     temp_folder_state = gr.State()

     # When loading subfolders, also store folder path in state
     load_btn.click(
        fn=load_subfolders_from_zip, 
        inputs=zip_input, 
        outputs=[folder_select, temp_folder_state, folder_status]
    )

    # Use stored folder path from gr.State for review
    run_zip_btn.click(
        fn=review_zip_selected, 
        inputs=[temp_folder_state, folder_select], 
        outputs=[zip_output_text, zip_report_file]
    )

    with gr.Tab("πŸ“„ Review Single File"):
        file_input = gr.File(label="Upload Single File", file_types=ALLOWED_EXTS)
        run_file_btn = gr.Button("πŸš€ Run Review")
        file_output_text = gr.Textbox(label="AI Review Output", lines=15)
        file_report_file = gr.File(label="Download DOCX Report", type="filepath")
        run_file_btn.click(fn=review_single_file, inputs=file_input, outputs=[file_output_text, file_report_file])

demo.launch()