jesshewyz commited on
Commit
483d121
·
verified ·
1 Parent(s): 2642f04

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +1 -12
  2. app.py +346 -0
  3. requirements.txt +136 -0
README.md CHANGED
@@ -1,12 +1 @@
1
- ---
2
- title: Code Review
3
- emoji: 🦀
4
- colorFrom: purple
5
- colorTo: green
6
- sdk: gradio
7
- sdk_version: 5.20.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # code_review_tool
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import psycopg2
4
+ from psycopg2.extras import RealDictCursor
5
+ from dotenv import load_dotenv
6
+ from langtrace_python_sdk.utils.with_root_span import with_langtrace_root_span
7
+ import openai
8
+ from contextlib import contextmanager
9
+
10
+ load_dotenv() # Load environment variables
11
+
12
+ GPT_4O_MINI = "gpt-4o-mini"
13
+ GPT_4 = "chatgpt-4o-latest"
14
+ O1_MINI = "o1-mini"
15
+
16
+ # Global cache for prompts
17
+ cached_prompts = {}
18
+
19
+ @contextmanager
20
+ def openai_session():
21
+ """Context manager to properly handle OpenAI API sessions"""
22
+ try:
23
+ # Initialize client
24
+ client = openai.OpenAI()
25
+ yield client
26
+ finally:
27
+ # Clean up client resources
28
+ if hasattr(client, 'close'):
29
+ client.close()
30
+
31
+ @with_langtrace_root_span()
32
+ def call_model(prompt, model):
33
+ print(f"calling {model} with prompt: {prompt[:100]}")
34
+ with openai_session() as client:
35
+ try:
36
+ # Call API
37
+ response = client.chat.completions.create(
38
+ model=model,
39
+ messages=[{"role": "user", "content": prompt}]
40
+ )
41
+ # Extract response text
42
+ result = response.choices[0].message.content
43
+ return result
44
+
45
+ except Exception as e:
46
+ return f"Error generating output: {str(e)}"
47
+
48
+ def get_db_connection():
49
+ """Establishes and returns a new database connection."""
50
+ db_params = {
51
+ 'dbname': os.getenv('DB_NAME'),
52
+ 'user': os.getenv('DB_USER'),
53
+ 'password': os.getenv('DB_PASSWORD'),
54
+ 'host': os.getenv('DB_HOST'),
55
+ 'port': os.getenv('DB_PORT')
56
+ }
57
+ conn = psycopg2.connect(**db_params)
58
+ return conn
59
+
60
+ def load_prompts():
61
+ """Fetches prompts from the DB and caches them as a dict mapping prompt name to a dict with body and id."""
62
+ global cached_prompts
63
+ conn = get_db_connection()
64
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
65
+ cursor.execute("""
66
+ SELECT code_review_prompt_name, code_review_prompt_body, code_review_prompt_id
67
+ FROM code_review_prompt
68
+ ORDER BY code_review_prompt_name;
69
+ """)
70
+ rows = cursor.fetchall()
71
+ # Cache format: { prompt_name: {"body": prompt_body, "id": prompt_id} }
72
+ cached_prompts = { row["code_review_prompt_name"]: {"body": row["code_review_prompt_body"], "id": row["code_review_prompt_id"]} for row in rows }
73
+ cursor.close()
74
+ conn.close()
75
+ return cached_prompts
76
+
77
+ # Initially load prompts
78
+ load_prompts()
79
+
80
+ def update_prompt_text(selected_prompt):
81
+ """Uses the global cache to return the prompt body for the selected prompt."""
82
+ return cached_prompts.get(selected_prompt, {}).get("body", "")
83
+
84
+ def build_final_prompt(custom_prompt, input_code):
85
+ """
86
+ Builds the final prompt for code review.
87
+ """
88
+ return (
89
+ f"Please review the following code:\n\n"
90
+ f"{custom_prompt}\n\n"
91
+ f"<CODE>\n"
92
+ f"{input_code}\n"
93
+ )
94
+
95
+ def store_submission(prompt_name, custom_prompt, input_code, review_output):
96
+ """
97
+ Stores the submission in the database.
98
+ """
99
+ prompt_data = cached_prompts.get(prompt_name)
100
+ prompt_id = prompt_data["id"] if prompt_data else None
101
+
102
+ conn = get_db_connection()
103
+ cursor = conn.cursor()
104
+ cursor.execute("""
105
+ INSERT INTO code_review_submission (code_review_prompt_id, custom_prompt, input_code, ai_review)
106
+ VALUES (%s, %s, %s, %s);
107
+ """, (prompt_id, custom_prompt, input_code, review_output))
108
+ conn.commit()
109
+ cursor.close()
110
+ conn.close()
111
+
112
+ def generate_ai_review(prompt_name, custom_prompt, input_code, model):
113
+ """
114
+ Generates an AI review by calling the API, then stores the submission.
115
+ Uses prompt_name to look up the prompt id.
116
+ """
117
+ final_prompt = build_final_prompt(custom_prompt, input_code)
118
+ review_output = call_model(final_prompt, model)
119
+ store_submission(prompt_name, custom_prompt, input_code, review_output)
120
+ return review_output
121
+
122
+ def call_four(prompt_name, custom_prompt, input_code):
123
+ return generate_ai_review(prompt_name, custom_prompt, input_code, GPT_4)
124
+
125
+ def call_o1(prompt_name, custom_prompt, input_code):
126
+ return generate_ai_review(prompt_name, custom_prompt, input_code, O1_MINI)
127
+
128
+ def call_four_mini(prompt_name, custom_prompt, input_code):
129
+ return generate_ai_review(prompt_name, custom_prompt, input_code, GPT_4O_MINI)
130
+
131
+ def add_new_prompt(prompt_name, prompt_body):
132
+ """Inserts a new prompt into the database, reloads the cache, and returns updated dropdown choices."""
133
+ if not prompt_name.strip() or not prompt_body.strip():
134
+ return "Error: Prompt Name and Body cannot be empty!", None, None
135
+
136
+ conn = None
137
+ try:
138
+ conn = get_db_connection()
139
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
140
+ cursor.execute("""
141
+ INSERT INTO code_review_prompt (code_review_prompt_name, code_review_prompt_body)
142
+ VALUES (%s, %s)
143
+ ON CONFLICT (code_review_prompt_name) DO NOTHING
144
+ RETURNING code_review_prompt_id;
145
+ """, (prompt_name.strip(), prompt_body.strip()))
146
+ inserted = cursor.fetchone()
147
+ if inserted:
148
+ conn.commit()
149
+ message = f"Prompt '{prompt_name}' added successfully!"
150
+ else:
151
+ message = f"Prompt '{prompt_name}' already exists!"
152
+ cursor.close()
153
+ conn.close()
154
+ # Reload the prompt cache after insertion
155
+ load_prompts()
156
+ new_choices = list(cached_prompts.keys())
157
+ new_dropdown = gr.Dropdown(choices=new_choices, label="Select Review Type", value=new_choices[0] if new_choices else None)
158
+ return message, new_dropdown, ""
159
+ except psycopg2.IntegrityError:
160
+ conn.rollback()
161
+ return "Prompt already exists!", None, ""
162
+ except Exception as e:
163
+ if conn:
164
+ conn.rollback()
165
+ return f"Error: {str(e)}", None, ""
166
+ finally:
167
+ if conn:
168
+ conn.close()
169
+
170
+ def reload_prompts():
171
+ """Reloads the prompt cache and returns a new Dropdown with updated choices."""
172
+ load_prompts()
173
+ new_choices = list(cached_prompts.keys())
174
+ new_dropdown = gr.Dropdown(choices=new_choices, label="Select Review Type", value=new_choices[0] if new_choices else None)
175
+ return new_dropdown
176
+
177
+ # ---------------------- New Code for Loading Previous Submissions ---------------------- #
178
+ def load_submissions_from_db():
179
+ """Fetch all submissions ordered by their id in ascending order."""
180
+ conn = get_db_connection()
181
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
182
+ cursor.execute("""
183
+ SELECT code_review_submission_id, code_review_prompt_id, custom_prompt, input_code, ai_review
184
+ FROM code_review_submission
185
+ ORDER BY created_at DESC; """)
186
+ submissions = cursor.fetchall()
187
+ cursor.close()
188
+ conn.close()
189
+ return submissions
190
+
191
+ def show_submission(index, submissions):
192
+ """Returns formatted outputs for the submission at the given index:
193
+ - Submission details (Markdown)
194
+ - Input code (for gr.Code)
195
+ - AI review output (Markdown)
196
+ """
197
+ if not submissions:
198
+ return "No submissions available", "", "No submissions available", index
199
+ # Clamp index within bounds
200
+ index = max(0, min(index, len(submissions) - 1))
201
+ submission = submissions[index]
202
+ # Left panel Markdown for submission details
203
+ submission_details = (
204
+ f"**Submission ID:** {submission['code_review_submission_id']}\n\n"
205
+ f"**Prompt ID:** {submission['code_review_prompt_id']}\n\n"
206
+ f"**Custom Prompt:**\n{submission['custom_prompt']}\n\n"
207
+ )
208
+ # Input code for gr.Code
209
+ input_code = submission['input_code']
210
+ # Right panel Markdown for AI review output
211
+ ai_review_output = f"**AI Review Output:**\n{submission['ai_review']}\n"
212
+ return submission_details, input_code, ai_review_output, index
213
+
214
+
215
+ def load_submissions():
216
+ """Loads submissions from the database and resets the index."""
217
+ submissions = load_submissions_from_db()
218
+ index = 0
219
+ details, code, review, index = show_submission(index, submissions)
220
+ return submissions, index, details, code, review
221
+
222
+ def next_submission(current_index, submissions):
223
+ """Increment the index and display the next submission."""
224
+ if submissions and current_index < len(submissions) - 1:
225
+ current_index += 1
226
+ details, code, review, current_index = show_submission(current_index, submissions)
227
+ return current_index, details, code, review
228
+
229
+ def previous_submission(current_index, submissions):
230
+ """Decrement the index and display the previous submission."""
231
+ if submissions and current_index > 0:
232
+ current_index -= 1
233
+ details, code, review, current_index = show_submission(current_index, submissions)
234
+ return current_index, details, code, review
235
+
236
+ # ----------------------------------------------------------------------------------------- #
237
+
238
+ # Gradio UI
239
+ with gr.Blocks() as demo:
240
+ gr.Markdown("# Code Review Assistant")
241
+
242
+ with gr.Tab("Code Review"):
243
+ with gr.Row():
244
+ with gr.Column(scale=1):
245
+ prompt_dropdown = gr.Dropdown(
246
+ choices=list(cached_prompts.keys()),
247
+ label="Select Review Type",
248
+ value=list(cached_prompts.keys())[0] if cached_prompts else None
249
+ )
250
+ with gr.Accordion("Selected Prompt", open=False):
251
+ prompt_textbox = gr.Textbox(label="", lines=15, interactive=True)
252
+ with gr.Row():
253
+ generate_4o_btn = gr.Button("Generate Code Review 4o")
254
+ generate_4omini_btn = gr.Button("Generate Code Review 4o-mini")
255
+ generate_o1_btn = gr.Button("Generate Code Review o1-mini")
256
+ with gr.Row():
257
+ input_code = gr.Code(language="python", label="Input Code", lines=15, interactive=True)
258
+ output_review = gr.Markdown(label="AI Review Output", container=True, show_copy_button=True)
259
+
260
+ with gr.Tab("Manage Prompts"):
261
+ with gr.Row():
262
+ new_prompt_name = gr.Textbox(label="New Prompt Name", placeholder="Enter a prompt name")
263
+ new_prompt_body = gr.Textbox(label="New Prompt Body", lines=3, placeholder="Enter the prompt details")
264
+ add_prompt_btn = gr.Button("Add Prompt")
265
+ reload_prompt_btn = gr.Button("Reload Prompts")
266
+ prompt_status = gr.Textbox(label="Status", interactive=False)
267
+
268
+ with gr.Tab("Submissions"):
269
+ with gr.Row():
270
+ load_submissions_btn = gr.Button("Load Submissions")
271
+ # Hidden states to store submissions and the current index
272
+ submissions_state = gr.State([])
273
+ submission_index_state = gr.State(0)
274
+ # Create two columns: left column for details and code, right column for AI review output
275
+ with gr.Row():
276
+ back_btn = gr.Button("Back")
277
+ next_btn = gr.Button("Next")
278
+ with gr.Row():
279
+ with gr.Column():
280
+ with gr.Accordion("Prompt Details", open= False):
281
+ submission_details_md = gr.Markdown(label="Submission Details")
282
+ input_code_component = gr.Code(label="Input Code", language="python")
283
+ with gr.Column():
284
+ ai_review_md = gr.Markdown(label="AI Review Output", container=True, show_copy_button=True)
285
+
286
+ # Load submissions from DB and initialize state
287
+ load_submissions_btn.click(
288
+ fn=load_submissions,
289
+ inputs=[],
290
+ outputs=[submissions_state, submission_index_state, submission_details_md, input_code_component, ai_review_md]
291
+ )
292
+ # Go to previous submission
293
+ back_btn.click(
294
+ fn=previous_submission,
295
+ inputs=[submission_index_state, submissions_state],
296
+ outputs=[submission_index_state, submission_details_md, input_code_component, ai_review_md]
297
+ )
298
+ # Go to next submission
299
+ next_btn.click(
300
+ fn=next_submission,
301
+ inputs=[submission_index_state, submissions_state],
302
+ outputs=[submission_index_state, submission_details_md, input_code_component, ai_review_md]
303
+ )
304
+
305
+
306
+
307
+ # When dropdown changes, update the prompt text
308
+ prompt_dropdown.change(
309
+ fn=update_prompt_text,
310
+ inputs=[prompt_dropdown],
311
+ outputs=[prompt_textbox]
312
+ )
313
+
314
+ # On code review generation, pass both the dropdown (prompt name) and the editable prompt text
315
+ generate_4o_btn.click(
316
+ fn=call_four,
317
+ inputs=[prompt_dropdown, prompt_textbox, input_code],
318
+ outputs=[output_review]
319
+ )
320
+ generate_4omini_btn.click(
321
+ fn=call_four_mini,
322
+ inputs=[prompt_dropdown, prompt_textbox, input_code],
323
+ outputs=[output_review]
324
+ )
325
+ generate_o1_btn.click(
326
+ fn=call_o1,
327
+ inputs=[prompt_dropdown, prompt_textbox, input_code],
328
+ outputs=[output_review]
329
+ )
330
+
331
+ # On adding a new prompt, update status, replace dropdown with a new one, and clear new prompt body textbox.
332
+ add_prompt_btn.click(
333
+ fn=add_new_prompt,
334
+ inputs=[new_prompt_name, new_prompt_body],
335
+ outputs=[prompt_status, prompt_dropdown, new_prompt_body]
336
+ )
337
+
338
+ # Reload prompts when the reload button is clicked.
339
+ reload_prompt_btn.click(
340
+ fn=reload_prompts,
341
+ inputs=[],
342
+ outputs=[prompt_dropdown]
343
+ )
344
+
345
+
346
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.12.0
2
+ gradio_client==1.5.4
3
+ huggingface-hub==0.27.1
4
+ hyperframe==6.0.1
5
+ openai==1.59.6
6
+ psycopg2-binary==2.9.10
7
+ pyasn1==0.6.1
8
+ pyasn1_modules==0.4.1
9
+ pydantic==2.10.5
10
+ pydantic_core==2.27.2
11
+ pydub==0.25.1
12
+ Pygments==2.19.1
13
+ pypandoc==1.15
14
+ pyparsing==3.2.1
15
+ python-dateutil==2.9.0.post0
16
+ python-docx==1.1.2
17
+ python-dotenv==1.0.1
18
+ python-multipart==0.0.20
19
+ pytz==2024.2
20
+ PyYAML==6.0.2
21
+ supabase==2.11.0
22
+ supafunc==0.9.0
23
+ aiofiles==23.2.1
24
+ aiohappyeyeballs==2.5.0
25
+ aiohttp==3.11.13
26
+ aiosignal==1.3.2
27
+ annotated-types==0.7.0
28
+ anyio==4.8.0
29
+ attrs==25.1.0
30
+ boto3==1.37.7
31
+ botocore==1.37.7
32
+ certifi==2025.1.31
33
+ charset-normalizer==3.4.1
34
+ click==8.1.8
35
+ colorama==0.4.6
36
+ Deprecated==1.2.18
37
+ deprecation==2.1.0
38
+ distro==1.9.0
39
+ fastapi==0.115.11
40
+ ffmpy==0.5.0
41
+ filelock==3.17.0
42
+ frozenlist==1.5.0
43
+ fsspec==2025.2.0
44
+ googleapis-common-protos==1.69.0
45
+ gotrue==2.11.4
46
+ gradio==5.12.0
47
+ gradio_client==1.5.4
48
+ groovy==0.1.2
49
+ grpcio==1.70.0
50
+ h11==0.14.0
51
+ h2==4.1.0
52
+ hpack==4.1.0
53
+ httpcore==1.0.7
54
+ httpx==0.27.2
55
+ huggingface-hub==0.27.1
56
+ hyperframe==6.0.1
57
+ idna==3.10
58
+ importlib_metadata==8.5.0
59
+ Jinja2==3.1.6
60
+ jiter==0.8.2
61
+ jmespath==1.0.1
62
+ langtrace-python-sdk==3.8.1
63
+ lxml==5.3.1
64
+ markdown-it-py==3.0.0
65
+ MarkupSafe==2.1.5
66
+ mdurl==0.1.2
67
+ multidict==6.1.0
68
+ numpy==2.2.3
69
+ openai==1.59.6
70
+ opentelemetry-api==1.30.0
71
+ opentelemetry-exporter-otlp-proto-common==1.30.0
72
+ opentelemetry-exporter-otlp-proto-grpc==1.30.0
73
+ opentelemetry-exporter-otlp-proto-http==1.30.0
74
+ opentelemetry-instrumentation==0.51b0
75
+ opentelemetry-instrumentation-sqlalchemy==0.51b0
76
+ opentelemetry-proto==1.30.0
77
+ opentelemetry-sdk==1.30.0
78
+ opentelemetry-semantic-conventions==0.51b0
79
+ orjson==3.10.15
80
+ packaging==24.2
81
+ pandas==2.2.3
82
+ pillow==11.1.0
83
+ postgrest==0.19.3
84
+ propcache==0.3.0
85
+ protobuf==5.29.3
86
+ psycopg2==2.9.10
87
+ psycopg2-binary==2.9.10
88
+ pyasn1==0.6.1
89
+ pyasn1_modules==0.4.1
90
+ pydantic==2.10.5
91
+ pydantic_core==2.27.2
92
+ pydub==0.25.1
93
+ Pygments==2.19.1
94
+ pypandoc==1.15
95
+ pyparsing==3.2.1
96
+ python-dateutil==2.9.0.post0
97
+ python-docx==1.1.2
98
+ python-dotenv==1.0.1
99
+ python-multipart==0.0.20
100
+ pytz==2024.2
101
+ PyYAML==6.0.2
102
+ realtime==2.4.1
103
+ regex==2024.11.6
104
+ requests==2.32.3
105
+ rich==13.9.4
106
+ ruff==0.9.9
107
+ s3transfer==0.11.4
108
+ safehttpx==0.1.6
109
+ safetensors==0.5.3
110
+ semantic-version==2.10.0
111
+ sentry-sdk==2.22.0
112
+ shellingham==1.5.4
113
+ six==1.17.0
114
+ sniffio==1.3.1
115
+ SQLAlchemy==2.0.38
116
+ starlette==0.46.0
117
+ storage3==0.11.3
118
+ StrEnum==0.4.15
119
+ supabase==2.11.0
120
+ supafunc==0.9.0
121
+ tiktoken==0.9.0
122
+ tokenizers==0.21.0
123
+ tomlkit==0.13.2
124
+ tqdm==4.67.1
125
+ trace-attributes==7.2.0
126
+ transformers==4.49.0
127
+ typer==0.15.2
128
+ typing_extensions==4.12.2
129
+ tzdata==2025.1
130
+ ujson==5.10.0
131
+ urllib3==2.3.0
132
+ uvicorn==0.34.0
133
+ websockets==14.2
134
+ wrapt==1.17.2
135
+ yarl==1.18.3
136
+ zipp==3.21.0