Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import httpx | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") | |
| GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}" | |
| async def generate_change_summary(files: list) -> str: | |
| """ | |
| Generate a concise summary of what changed in the PR using AI. | |
| """ | |
| logger.info(f"π Generating change summary for {len(files)} files...") | |
| # Build a summary of changes | |
| changes_text = "" | |
| for f in files: | |
| status = f.get("status", "modified") | |
| filename = f.get("filename", "unknown") | |
| additions = f.get("additions", 0) | |
| deletions = f.get("deletions", 0) | |
| changes_text += f"- {status.upper()}: {filename} (+{additions}/-{deletions})\n" | |
| prompt = f"""You are a code reviewer. Summarize what changed in this pull request in 1-2 short sentences. | |
| Focus on WHAT was changed, not HOW. Be concise and clear. | |
| Files changed: | |
| {changes_text} | |
| Respond with ONLY the summary text (no markdown, no extra formatting):""" | |
| headers = {"Content-Type": "application/json"} | |
| payload = { | |
| "contents": [{"parts": [{"text": prompt}]}], | |
| "generationConfig": { | |
| "temperature": 0.5, | |
| "maxOutputTokens": 150 | |
| } | |
| } | |
| try: | |
| async with httpx.AsyncClient(timeout=30.0) as client: | |
| response = await client.post(GEMINI_API_URL, headers=headers, json=payload) | |
| response.raise_for_status() | |
| data = response.json() | |
| summary = data["candidates"][0]["content"]["parts"][0]["text"].strip() | |
| logger.info(f"β Generated summary: {summary[:100]}...") | |
| return summary | |
| except Exception as e: | |
| logger.warning(f"β οΈ Failed to generate summary: {str(e)}") | |
| return f"Modified {len(files)} file(s)" | |
| async def analyze_code(file_name: str, patch: str) -> list: | |
| """ | |
| Analyze a single file diff using Google Gemini AI model. | |
| Returns a list of structured comments. | |
| """ | |
| logger.info(f"π€ Sending to Gemini AI: {file_name} ({len(patch)} chars)") | |
| prompt = f"""You are a senior code reviewer focused on finding REAL issues. | |
| Review the following diff from `{file_name}` and provide feedback ONLY for: | |
| - Security vulnerabilities | |
| - Bugs or logic errors | |
| - Performance issues | |
| - Code that will break in production | |
| - Missing error handling for critical operations | |
| - Resource leaks (memory, connections, files) | |
| DO NOT comment on: | |
| - Code style or formatting | |
| - Comments or documentation | |
| - Variable naming (unless critically confusing) | |
| - Minor suggestions or preferences | |
| - Things that are already working fine | |
| Respond ONLY with a JSON array (no markdown, no explanation): | |
| [ | |
| {{ | |
| "line": 42, | |
| "severity": "high", | |
| "comment": "Potential SQL injection vulnerability - use parameterized queries" | |
| }} | |
| ] | |
| Severity levels: "high" (critical bugs/security), "medium" (bugs/performance), "low" (minor issues) | |
| If no REAL issues found, return an empty array: [] | |
| Code Diff: | |
| {patch} | |
| """ | |
| headers = { | |
| "Content-Type": "application/json" | |
| } | |
| payload = { | |
| "contents": [ | |
| { | |
| "parts": [ | |
| {"text": prompt} | |
| ] | |
| } | |
| ], | |
| "generationConfig": { | |
| "temperature": 0.3, | |
| "maxOutputTokens": 2048 | |
| } | |
| } | |
| try: | |
| async with httpx.AsyncClient(timeout=60.0) as client: | |
| logger.info("β³ Waiting for Gemini response...") | |
| response = await client.post(GEMINI_API_URL, headers=headers, json=payload) | |
| response.raise_for_status() | |
| data = response.json() | |
| logger.info("β Gemini response received") | |
| # Extract text from Gemini response structure | |
| text_output = data["candidates"][0]["content"]["parts"][0]["text"].strip() | |
| logger.info(f"π Response length: {len(text_output)} chars") | |
| # Defensive: handle non-JSON outputs | |
| try: | |
| # Remove markdown code blocks if present | |
| if text_output.startswith("```json"): | |
| text_output = text_output.replace("```json", "").replace("```", "").strip() | |
| elif text_output.startswith("```"): | |
| text_output = text_output.replace("```", "").strip() | |
| parsed = json.loads(text_output) | |
| logger.info(f"β Parsed {len(parsed)} review comments") | |
| return parsed | |
| except Exception as e: | |
| logger.warning(f"β οΈ Failed to parse JSON, returning raw text: {str(e)}") | |
| logger.warning(f"Raw response: {text_output[:200]}") | |
| return [{"line": 1, "severity": "info", "comment": text_output}] | |
| except Exception as e: | |
| logger.error(f"β Gemini API error: {str(e)}") | |
| raise | |