├── Procfile ├── .env ├── requirements.txt ├── utils ├── github_handler.py └── notebook_handler.py ├── config └── settings.py ├── README.md ├── templates ├── chat.html └── base.html ├── formatters └── output_formatter.py ├── static ├── js │ └── main.js └── css │ └── style.css ├── app.py ├── database └── chat_history.py └── analyzers └── code_analyzer.py /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY="api-key-here" 2 | GEMINI_API_KEY="api-key-here" 3 | DEFAULT_AI_SERVICE=auto -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gunicorn 2 | openai==1.35.13 3 | flask==3.0.3 4 | google-generativeai==0.8.3 5 | requests==2.31.0 6 | nbformat==5.10.4 7 | python-dotenv==1.0.1 -------------------------------------------------------------------------------- /utils/github_handler.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | 4 | class GitHubHandler: 5 | @staticmethod 6 | def get_raw_github_url(github_url): 7 | """Normal GitHub URL'sini raw içerik URL'sine dönüştürür""" 8 | pattern = r'https://github\.com/([^/]+)/([^/]+)/blob/([^/]+)/(.+)' 9 | match = re.match(pattern, github_url) 10 | 11 | if not match: 12 | raise ValueError("Geçersiz GitHub URL'si") 13 | 14 | user, repo, branch, path = match.groups() 15 | raw_url = f'https://raw.githubusercontent.com/{user}/{repo}/{branch}/{path}' 16 | return raw_url 17 | 18 | @staticmethod 19 | def get_file_content(url): 20 | """GitHub URL'inden dosya içeriğini alır""" 21 | try: 22 | raw_url = GitHubHandler.get_raw_github_url(url) 23 | response = requests.get(raw_url) 24 | response.raise_for_status() 25 | return response.text 26 | 27 | except requests.exceptions.RequestException as e: 28 | raise Exception(f"Dosya alınırken hata oluştu: {str(e)}") -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import codecs 4 | 5 | sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer) 6 | 7 | DEFAULT_AI_SERVICE = os.getenv('DEFAULT_AI_SERVICE', 'auto') 8 | 9 | OPENAI_MODEL = "gpt-4o-mini" 10 | TEMPERATURE = 0.7 11 | 12 | ANALYSIS_TEMPLATE = { 13 | "proje_amaci": "Analiz sırasında bir hata oluştu", 14 | "proje_ozeti": "JSON formatı elde edilemedi", 15 | "kullanilan_teknolojiler": ["Belirlenemedi"], 16 | "genel_degerlendirme": "GPT yanıtı JSON formatında değildi", 17 | "guclu_yonler": ["Belirlenemedi"], 18 | "iyilestirme_alanlari": ["Belirlenemedi"], 19 | "kod_ornekleri": ["Belirlenemedi"], 20 | "guvenlik_onerileri": ["Belirlenemedi"], 21 | "performans_onerileri": ["Belirlenemedi"] 22 | } 23 | 24 | SYSTEM_PROMPT = """Sen senior bir Python geliştiricisisin. Verilen kodu ve Markdown dokümantasyonunu aşağıdaki kriterlere göre analiz et ve SADECE JSON formatında yanıt ver. Açıklamalar JSON içinde olmalı, dışında hiçbir metin olmamalı. 25 | 26 | Özellikle kod örnekleri kısmında, her bir öneri için önce açıklama, sonra çalışabilir kod örneği ver. Her bir kod örneği şu formatı takip etmeli: 27 | 28 | "kod_ornekleri": [ 29 | { 30 | "aciklama": "Bu iyileştirme, mevcut fonksiyonun performansını artırır ve bellek kullanımını optimize eder. List comprehension kullanarak döngü yerine daha pythonic bir yaklaşım sunar.", 31 | "kod": "def optimize_function(data):\\n # Optimize edilmiş versiyon\\n result = [x * 2 for x in data]\\n return result" 32 | }, 33 | { 34 | "aciklama": "Cache mekanizması ekleyerek tekrarlanan işlemleri önler ve performansı artırır. Bu özellikle ağır işlemler için önemli bir optimizasyondur.", 35 | "kod": "class BetterImplementation:\\n def __init__(self):\\n self.cache = {}\\n\\n def process(self, data):\\n return self.cache.get(data, self._process(data))" 36 | } 37 | ] 38 | 39 | Yanıt formatı tam olarak şöyle olmalı: 40 | { 41 | "proje_amaci": "Projenin ana amacının kısa açıklaması", 42 | "proje_ozeti": "Projenin nasıl çalıştığına dair kısa özet", 43 | "kullanilan_teknolojiler": ["Teknoloji 1", "Teknoloji 2"], 44 | "genel_degerlendirme": "Kodun genel kalitesi hakkında kısa özet", 45 | "guclu_yonler": ["Güçlü yön 1", "Güçlü yön 2"], 46 | "iyilestirme_alanlari": ["İyileştirme 1", "İyileştirme 2"], 47 | "kod_ornekleri": [ 48 | { 49 | "aciklama": "İyileştirmenin açıklaması", 50 | "kod": "Örnek kod" 51 | } 52 | ], 53 | "guvenlik_onerileri": ["Güvenlik önerisi 1", "Güvenlik önerisi 2"], 54 | "performans_onerileri": ["Performans önerisi 1", "Performans önerisi 2"] 55 | }""" 56 | 57 | """ 58 | Lütfen şunları analiz et: 59 | 1. Proje Analizi: 60 | - Projenin amacı ve kapsamı 61 | - Kullanılan temel teknolojiler ve kütüphaneler 62 | - Projenin kısa özeti 63 | 64 | 2. Kod Kalitesi: 65 | - Okunabilirlik 66 | - Maintainability (Sürdürülebilirlik) 67 | - Best practices uyumu 68 | - PEP 8 standartlarına uyum 69 | 70 | 3. Potansiyel İyileştirmeler: 71 | - Performans optimizasyonları 72 | - Kod organizasyonu 73 | - Hata yönetimi 74 | - Güvenlik konuları 75 | 76 | 4. Öneriler: 77 | - Spesifik kod örnekleriyle birlikte iyileştirme önerileri 78 | - Modern Python özelliklerinin kullanımı 79 | - Alternatif yaklaşımlar 80 | """ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Feedback Agent 2 | 3 | ![Python Version](https://img.shields.io/badge/python-3.10.6-blue.svg) ![Flask Version](https://img.shields.io/badge/flask-3.0.3-green.svg) ![OpenAI](https://img.shields.io/badge/OpenAI-GPT4-orange.svg) ![Gemini](https://img.shields.io/badge/Gemini-pro-purple.svg) ![License](https://img.shields.io/badge/license-MIT-blue.svg) 4 | 5 | 6 | Code Feedback Agent is an interactive web application that analyzes your Python projects on GitHub and provides AI-powered feedback. You can receive detailed analysis of your project and chat with the bot to improve your code. 7 | 8 | ### 🌐 Demo 9 | 10 | #### [https://feedback-agent.onrender.com/](https://feedback-agent.onrender.com/) 11 | 12 | ## 🚀 Features 13 | 14 | - 📊 Comprehensive code analysis 15 | - 💬 Interactive chat interface 16 | - 📝 Markdown formatted outputs 17 | - 🔄 Chat history tracking 18 | - 🐍 Python and Jupyter Notebook support 19 | - 🔒 Secure and scalable architecture 20 | 21 | ## 🛠️ Technologies 22 | 23 | - **Backend**: Flask 24 | - **AI**: OpenAI GPT-4 & Gemini Pro 25 | - **Database**: SQLite 26 | - **Frontend**: HTML, CSS (Tailwind), JavaScript 27 | 28 | ## 📋 Prerequisites 29 | 30 | - Python 3.10+ 31 | - OpenAI & Gemini API key 32 | - Git & pip 33 | 34 | ## 🔧 Installation 35 | 36 | 1. Clone the repository: 37 | ```bash 38 | git clone https://github.com/enesmanan/feedback-agent.git 39 | cd feedback-agent 40 | ``` 41 | 42 | 2. Create a virtual environment: 43 | ```bash 44 | python -m venv venv 45 | source venv/bin/activate # Linux/Mac 46 | venv\Scripts\activate # Windows 47 | ``` 48 | 49 | 3. Install requirements: 50 | ```bash 51 | pip install -r requirements.txt 52 | ``` 53 | 54 | 4. Set `.env` file: 55 | ```env 56 | OPENAI_API_KEY="api-key-here" 57 | GEMINI_API_KEY="api-key-here" 58 | DEFAULT_AI_SERVICE=auto 59 | ``` 60 | 61 | 5. Run the application: 62 | ```bash 63 | python3 app.py # Linux 64 | python app.py # Windows 65 | ``` 66 | 67 | ## 💡 Usage 68 | 69 | 1. Go to `http://localhost:5000` in your browser 70 | 2. Enter your GitHub project URL 71 | 3. Review the analysis results 72 | 4. Chat with the bot to improve your project 73 | 74 | ## 📁 Project Structure 75 | 76 | ``` 77 | feedback_agent/ 78 | ├── templates/ # HTML templates 79 | │ ├── base.html 80 | │ └── chat.html 81 | ├── static/ # Static files 82 | │ ├── css/ 83 | │ │ └── style.css 84 | │ └── js/ 85 | │ └── main.js 86 | ├── database/ # Database operations 87 | │ └── chat_history.py 88 | ├── utils/ # Utility functions 89 | │ ├── github_handler.py 90 | │ └── notebook_handler.py 91 | ├── analyzers/ # Code analysis 92 | │ └── code_analyzer.py 93 | ├── formatters/ # Output formatting 94 | │ └── output_formatter.py 95 | ├── config/ # Configuration 96 | │ └── settings.py 97 | ├── app.py # Main application 98 | ├── requirements.txt # Dependencies 99 | └── README.md # Documentation 100 | ``` 101 | 102 | 103 | ## 🤝 Contributing 104 | 105 | 1. Fork the Project 106 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 107 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 108 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 109 | 5. Open a Pull Request 110 | 111 | 112 | ## 📝 License 113 | 114 | This project is licensed under the MIT License - see the [LICENSE](https://mit-license.org/) file for details. 115 | 116 | ## 👥 Contact 117 | 118 | Enes Fehmi Manan - [@enesfehmimanan](https://www.linkedin.com/in/enesfehmimanan/) 119 | 120 | 121 | -------------------------------------------------------------------------------- /templates/chat.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 | 6 |
7 | 8 |
9 |
10 |

Analyze GitHub Repository

11 |
12 |
13 | 14 |
15 | 19 |
20 |
21 |
22 | 26 |
27 |
28 |
29 |
30 | 31 | 32 |
33 | 34 |
35 | {% if history %} 36 | {% for msg in history.messages %} 37 |
38 |
39 | {{ msg.message }} 40 |
41 |
42 |
43 |
44 | {{ msg.response }} 45 |
46 |
47 | {% endfor %} 48 | {% endif %} 49 |
50 | 51 | 52 |
53 |
55 | 59 | 64 |
65 |
66 |
67 |
68 |
69 | {% endblock %} -------------------------------------------------------------------------------- /formatters/output_formatter.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class OutputFormatter: 4 | @staticmethod 5 | def _format_code_block(text): 6 | """Kod bloklarını markdown formatına dönüştürür""" 7 | if "```" in text: 8 | return text 9 | 10 | # Kod kalıplarını kontrol et 11 | code_patterns = [ 12 | r'def\s+\w+\s*\([^)]*\)\s*:', 13 | r'class\s+\w+(\s*\([^)]*\))?\s*:', 14 | r'import\s+\w+', 15 | r'from\s+\w+\s+import', 16 | r'^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*=', 17 | r'if\s+__name__\s*==\s*[\'"]__main__[\'"]', 18 | r'for\s+\w+\s+in\s+', 19 | r'while\s+.*:', 20 | r'try:', 21 | r'except', 22 | r'return\s+' 23 | ] 24 | 25 | if any(re.search(pattern, text) for pattern in code_patterns): 26 | lines = text.split('\n') 27 | first_indent = None 28 | for line in lines: 29 | if line.strip(): 30 | first_indent = len(line) - len(line.lstrip()) 31 | break 32 | 33 | if first_indent is not None: 34 | normalized_lines = [] 35 | for line in lines: 36 | if line.strip(): 37 | line = line[first_indent:] if len(line) > first_indent else line.lstrip() 38 | normalized_lines.append(line) 39 | text = '\n'.join(normalized_lines) 40 | 41 | return f"```python\n{text.strip()}\n```" 42 | 43 | return text 44 | 45 | @staticmethod 46 | def format_analysis(analysis): 47 | """Analiz sonuçlarını markdown formatında döndürür""" 48 | if "error" in analysis: 49 | return f"## Hata\n{analysis['error']}" 50 | 51 | formatted_output = [] 52 | formatted_output.append("# Kod Analiz Raporu\n") 53 | 54 | # Proje Amacı 55 | formatted_output.append("## Proje Amacı") 56 | formatted_output.append(analysis['proje_amaci']) 57 | formatted_output.append("") 58 | 59 | # Proje Özeti 60 | formatted_output.append("## Proje Özeti") 61 | formatted_output.append(analysis['proje_ozeti']) 62 | formatted_output.append("") 63 | 64 | # Kullanılan Teknolojiler 65 | formatted_output.append("## Kullanılan Teknolojiler") 66 | for tech in analysis['kullanilan_teknolojiler']: 67 | formatted_output.append(f"* {tech}") 68 | formatted_output.append("") 69 | 70 | # Genel Değerlendirme 71 | formatted_output.append("## Genel Değerlendirme") 72 | formatted_output.append(analysis['genel_degerlendirme']) 73 | formatted_output.append("") 74 | 75 | # Güçlü Yönler 76 | formatted_output.append("## Güçlü Yönler") 77 | for item in analysis['guclu_yonler']: 78 | formatted_output.append(f"* {item}") 79 | formatted_output.append("") 80 | 81 | # İyileştirme Alanları 82 | formatted_output.append("## İyileştirme Alanları") 83 | for item in analysis['iyilestirme_alanlari']: 84 | formatted_output.append(f"* {item}") 85 | formatted_output.append("") 86 | 87 | # Kod Örnekleri ve Öneriler 88 | formatted_output.append("## Kod Örnekleri ve Öneriler") 89 | for item in analysis['kod_ornekleri']: 90 | # Yeni format için kontrol 91 | if isinstance(item, dict) and 'aciklama' in item and 'kod' in item: 92 | # Önce açıklamayı ekle 93 | formatted_output.append(f"### İyileştirme Açıklaması") 94 | formatted_output.append(item['aciklama']) 95 | formatted_output.append("") 96 | # Sonra kodu ekle 97 | formatted_output.append("### Örnek Kod") 98 | formatted_output.append(OutputFormatter._format_code_block(item['kod'])) 99 | formatted_output.append("") 100 | else: 101 | # Eski format için geriye dönük uyumluluk 102 | formatted_output.append(OutputFormatter._format_code_block(item)) 103 | formatted_output.append("") 104 | 105 | # Güvenlik Önerileri 106 | formatted_output.append("## Güvenlik Önerileri") 107 | for item in analysis['guvenlik_onerileri']: 108 | formatted_output.append(f"* {item}") 109 | formatted_output.append("") 110 | 111 | # Performans Önerileri 112 | formatted_output.append("## Performans Önerileri") 113 | for item in analysis['performans_onerileri']: 114 | formatted_output.append(f"* {item}") 115 | 116 | return "\n".join(formatted_output) -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | const md = window.markdownit({ 3 | highlight: function (str, lang) { 4 | if (lang && Prism.languages[lang]) { 5 | try { 6 | return '
' +
  7 |                            Prism.highlight(str, Prism.languages[lang], lang) +
  8 |                            '
'; 9 | } catch (__) {} 10 | } 11 | return '
' + md.utils.escapeHtml(str) + '
'; 12 | } 13 | }); 14 | 15 | const messagesDiv = document.getElementById('messages'); 16 | // URL'den conversation_id'yi al 17 | const urlParams = new URLSearchParams(window.location.pathname); 18 | let conversationId = document.getElementById('chatForm').dataset.conversationId || null; 19 | 20 | // Markdown içeriğini render et ve Prism.js'i başlat 21 | function renderMarkdownAndInitPrism(element) { 22 | element.innerHTML = md.render(element.textContent); 23 | Prism.highlightAllUnder(element); 24 | } 25 | 26 | // Mevcut markdown içeriğini işle 27 | document.querySelectorAll('.markdown-content').forEach(renderMarkdownAndInitPrism); 28 | 29 | // Mesajları en alta kaydır 30 | if (messagesDiv) { 31 | messagesDiv.scrollTop = messagesDiv.scrollHeight; 32 | } 33 | 34 | function addMessage(sender, content) { 35 | const messageDiv = document.createElement('div'); 36 | messageDiv.className = `message ${sender} mb-4`; 37 | 38 | const contentDiv = document.createElement('div'); 39 | contentDiv.className = sender === 'user' ? 40 | 'bg-blue-100 p-3 rounded-lg mx-auto max-w-3xl' : 41 | 'bg-white shadow-lg p-4 rounded-lg mx-auto max-w-3xl markdown-content'; 42 | 43 | if (sender === 'assistant') { 44 | contentDiv.textContent = content; 45 | renderMarkdownAndInitPrism(contentDiv); 46 | } else { 47 | contentDiv.textContent = content; 48 | } 49 | 50 | messageDiv.appendChild(contentDiv); 51 | messagesDiv.appendChild(messageDiv); 52 | messagesDiv.scrollTop = messagesDiv.scrollHeight; 53 | } 54 | 55 | // GitHub URL form submit 56 | const urlForm = document.getElementById('urlForm'); 57 | if (urlForm) { 58 | urlForm.addEventListener('submit', async function(e) { 59 | e.preventDefault(); 60 | const url = document.getElementById('githubUrl').value; 61 | 62 | try { 63 | const response = await fetch('/analyze', { 64 | method: 'POST', 65 | headers: { 66 | 'Content-Type': 'application/json' 67 | }, 68 | body: JSON.stringify({ url: url }) 69 | }); 70 | 71 | const data = await response.json(); 72 | conversationId = data.conversation_id; 73 | 74 | // URL input'u gizle 75 | document.getElementById('urlInput').style.display = 'none'; 76 | 77 | // Chat input'u aktif et 78 | document.getElementById('userMessage').disabled = false; 79 | document.getElementById('chatForm').querySelector('button').disabled = false; 80 | 81 | // Conversation ID'yi forma ekle 82 | document.getElementById('chatForm').dataset.conversationId = conversationId; 83 | 84 | // Analiz sonucunu göster 85 | addMessage('assistant', data.response); 86 | 87 | // URL'i güncelle (sayfa yenilenmeden) 88 | window.history.pushState({}, '', `/history/${conversationId}`); 89 | 90 | } catch (error) { 91 | console.error('Error:', error); 92 | alert('Bir hata oluştu!'); 93 | } 94 | }); 95 | } 96 | 97 | // Chat form submit 98 | const chatForm = document.getElementById('chatForm'); 99 | if (chatForm) { 100 | chatForm.addEventListener('submit', async function(e) { 101 | e.preventDefault(); 102 | const messageInput = document.getElementById('userMessage'); 103 | const message = messageInput.value; 104 | 105 | if (!message.trim()) return; 106 | 107 | const currentConversationId = this.dataset.conversationId || conversationId; 108 | 109 | if (!currentConversationId) { 110 | alert('Geçerli bir konuşma bulunamadı!'); 111 | return; 112 | } 113 | 114 | addMessage('user', message); 115 | messageInput.value = ''; 116 | 117 | try { 118 | const response = await fetch('/chat', { 119 | method: 'POST', 120 | headers: { 121 | 'Content-Type': 'application/json' 122 | }, 123 | body: JSON.stringify({ 124 | message: message, 125 | conversation_id: currentConversationId 126 | }) 127 | }); 128 | 129 | const data = await response.json(); 130 | if (data.error) { 131 | throw new Error(data.error); 132 | } 133 | addMessage('assistant', data.response); 134 | 135 | } catch (error) { 136 | console.error('Error:', error); 137 | alert('Bir hata oluştu!'); 138 | } 139 | }); 140 | } 141 | }); -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | .markdown-content { 2 | line-height: 1.6; 3 | } 4 | 5 | .markdown-content h1 { 6 | font-size: 1.5em; 7 | font-weight: bold; 8 | margin: 1em 0; 9 | } 10 | 11 | .markdown-content h2 { 12 | font-size: 1.3em; 13 | font-weight: bold; 14 | margin: 0.8em 0; 15 | } 16 | 17 | .markdown-content pre { 18 | margin: 1em 0; 19 | border-radius: 6px; 20 | box-shadow: 0 2px 4px rgba(211, 205, 205, 0.1); 21 | } 22 | 23 | .markdown-content code { 24 | font-family: 'Fira Code', monospace; 25 | border-radius: 3px; 26 | } 27 | 28 | .markdown-content p code { 29 | background-color: rgba(211, 205, 205, 0.1); 30 | padding: 0.2em 0.4em; 31 | } 32 | 33 | .markdown-content ul { 34 | list-style-type: disc; 35 | margin-left: 1.5em; 36 | margin-bottom: 1em; 37 | } 38 | 39 | .markdown-content ol { 40 | list-style-type: decimal; 41 | margin-left: 1.5em; 42 | margin-bottom: 1em; 43 | } 44 | 45 | /* Mevcut stiller korunacak, aşağıdaki stiller eklenecek */ 46 | 47 | /* Font ailesi */ 48 | .font-space-grotesk { 49 | font-family: 'Space Grotesk', sans-serif; 50 | } 51 | 52 | /* Sidebar stilleri */ 53 | #sidebar { 54 | box-shadow: 4px 0 6px -1px rgba(0, 0, 0, 0.1); 55 | } 56 | 57 | #sidebar .history-item { 58 | transition: all 0.2s ease; 59 | } 60 | 61 | #sidebar .history-item:hover { 62 | transform: translateX(4px); 63 | } 64 | 65 | /* Gradient başlık */ 66 | .gradient-title { 67 | background: linear-gradient(135deg, #4F46E5, #2563EB); 68 | -webkit-background-clip: text; 69 | -webkit-text-fill-color: transparent; 70 | } 71 | 72 | /* Message boxes */ 73 | .message .markdown-content { 74 | transition: transform 0.2s ease; 75 | } 76 | 77 | .message .markdown-content:hover { 78 | transform: translateY(-2px); 79 | box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); 80 | } 81 | 82 | /* Scrollbar */ 83 | .overflow-y-auto::-webkit-scrollbar { 84 | width: 6px; 85 | } 86 | 87 | .overflow-y-auto::-webkit-scrollbar-track { 88 | background: #f1f1f1; 89 | } 90 | 91 | .overflow-y-auto::-webkit-scrollbar-thumb { 92 | background: #cbd5e0; 93 | border-radius: 3px; 94 | } 95 | 96 | .overflow-y-auto::-webkit-scrollbar-thumb:hover { 97 | background: #a0aec0; 98 | } 99 | 100 | 101 | /* Prism.js özelleştirmeleri */ 102 | pre[class*="language-"] { 103 | background: #f5f5f5; /* Açık gri arka plan */ 104 | margin: 1em 0; 105 | padding: 1em; 106 | overflow: auto; 107 | border-radius: 6px; 108 | border: 1px solid #e0e0e0; 109 | } 110 | 111 | :not(pre) > code[class*="language-"], 112 | pre[class*="language-"] { 113 | background: #f5f5f5; /* Açık gri arka plan */ 114 | } 115 | 116 | code[class*="language-"] { 117 | color: #333; /* Koyu gri ana metin rengi */ 118 | text-shadow: none; /* Text shadow'u kaldır */ 119 | } 120 | 121 | /* Sözdizimi renklendirme */ 122 | .token.comment, 123 | .token.prolog, 124 | .token.doctype, 125 | .token.cdata { 126 | color: #666; /* Yorumlar için daha koyu gri */ 127 | font-style: italic; 128 | } 129 | 130 | .token.function { 131 | color: #0050b3; /* Fonksiyonlar için koyu mavi */ 132 | } 133 | 134 | .token.keyword { 135 | color: #8250df; /* Anahtar kelimeler için mor */ 136 | font-weight: 500; 137 | } 138 | 139 | .token.string { 140 | color: #0a7343; /* String'ler için koyu yeşil */ 141 | } 142 | 143 | .token.number { 144 | color: #cd5c5c; /* Sayılar için kırmızımsı */ 145 | } 146 | 147 | .token.operator { 148 | color: #0550ae; /* Operatörler için mavi */ 149 | } 150 | 151 | .token.class-name { 152 | color: #886ce4; /* Sınıf isimleri için mor */ 153 | } 154 | 155 | .token.variable { 156 | color: #953800; /* Değişkenler için turuncu-kahve */ 157 | } 158 | 159 | .token.property { 160 | color: #0550ae; /* Property'ler için mavi */ 161 | } 162 | 163 | .token.punctuation { 164 | color: #24292f; /* Noktalama işaretleri için koyu gri */ 165 | } 166 | 167 | /* Kod bloğu container stil güncellemesi */ 168 | .code-toolbar { 169 | position: relative; 170 | margin: 1em 0; 171 | } 172 | 173 | pre[class*="language-"] { 174 | box-shadow: 0 2px 8px rgba(0,0,0,0.05); 175 | } 176 | 177 | /* Copy buton stilleri */ 178 | .toolbar-item button { 179 | position: absolute; 180 | right: 0.5em; 181 | top: 0.5em; 182 | font-size: 0.8em; 183 | padding: 0.4em 0.8em; 184 | background: rgba(0,0,0,0.1); 185 | border: 1px solid rgba(0,0,0,0.1); 186 | border-radius: 4px; 187 | color: #333; 188 | cursor: pointer; 189 | transition: all 0.2s ease; 190 | } 191 | 192 | .toolbar-item button:hover { 193 | background: rgba(0,0,0,0.15); 194 | border-color: rgba(0,0,0,0.15); 195 | } 196 | 197 | /* Copy success message */ 198 | div.code-toolbar > .toolbar .toolbar-item > button:focus { 199 | outline: none; 200 | } 201 | 202 | div.code-toolbar > .toolbar .toolbar-item > button[data-copy-state="copy"] { 203 | background: rgba(0,0,0,0.1); 204 | } 205 | 206 | div.code-toolbar > .toolbar .toolbar-item > button[data-copy-state="copied"] { 207 | background: #28a745; 208 | color: white; 209 | } 210 | 211 | /* Seçim rengi */ 212 | pre[class*="language-"]::-moz-selection, 213 | pre[class*="language-"] ::-moz-selection, 214 | code[class*="language-"]::-moz-selection, 215 | code[class*="language-"] ::-moz-selection { 216 | background: #b3d4fc; 217 | } 218 | 219 | pre[class*="language-"]::selection, 220 | pre[class*="language-"] ::selection, 221 | code[class*="language-"]::selection, 222 | code[class*="language-"] ::selection { 223 | background: #b3d4fc; 224 | } 225 | 226 | /* Kod bloğu içindeki satır yüksekliği */ 227 | pre[class*="language-"] > code { 228 | line-height: 1.5; 229 | font-size: 0.9em; 230 | font-family: 'Fira Code', 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace; 231 | } 232 | 233 | /* Kod bloğu hover efekti */ 234 | pre[class*="language-"]:hover { 235 | box-shadow: 0 3px 10px rgba(0,0,0,0.08); 236 | } 237 | 238 | /* Özel scrollbar */ 239 | pre[class*="language-"]::-webkit-scrollbar { 240 | width: 8px; 241 | height: 8px; 242 | } 243 | 244 | pre[class*="language-"]::-webkit-scrollbar-track { 245 | background: #f1f1f1; 246 | border-radius: 4px; 247 | } 248 | 249 | pre[class*="language-"]::-webkit-scrollbar-thumb { 250 | background: #c1c1c1; 251 | border-radius: 4px; 252 | } 253 | 254 | pre[class*="language-"]::-webkit-scrollbar-thumb:hover { 255 | background: #a8a8a8; 256 | } -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, jsonify 2 | from database.chat_history import ChatHistory 3 | from utils.github_handler import GitHubHandler 4 | from utils.notebook_handler import NotebookHandler 5 | from analyzers.code_analyzer import CodeAnalyzer 6 | from formatters.output_formatter import OutputFormatter 7 | import os 8 | from dotenv import load_dotenv 9 | 10 | load_dotenv() 11 | 12 | class CodeFeedbackSystem: 13 | def __init__(self, api_keys): 14 | self.analyzer = CodeAnalyzer(api_keys) 15 | self.github_handler = GitHubHandler() 16 | self.notebook_handler = NotebookHandler() 17 | self.formatter = OutputFormatter() 18 | 19 | def analyze_code(self, github_url): 20 | """Analyze code from GitHub URL""" 21 | try: 22 | # Get content from GitHub 23 | content = self.github_handler.get_file_content(github_url) 24 | 25 | # Handle Jupyter notebooks 26 | if github_url.endswith('.ipynb'): 27 | notebook_data = self.notebook_handler.extract_notebook_code(content) 28 | analysis = self.analyzer.analyze_code( 29 | code=notebook_data['code'], 30 | notebook_data=notebook_data 31 | ) 32 | elif github_url.endswith('.py'): 33 | analysis = self.analyzer.analyze_code(code=content) 34 | else: 35 | raise ValueError("Unsupported file format. Only .py and .ipynb files are supported.") 36 | 37 | return self.formatter.format_analysis(analysis) 38 | 39 | except Exception as e: 40 | return f"Error occurred: {str(e)}" 41 | 42 | def chat_about_code(self, message, code_context): 43 | """Chat about code using AI""" 44 | try: 45 | return self.analyzer.chat_about_code(message, code_context) 46 | except Exception as e: 47 | return f"Chat error: {str(e)}" 48 | 49 | def create_app(): 50 | """Create and configure the Flask application""" 51 | app = Flask(__name__) 52 | 53 | # Configuration 54 | app.config.from_mapping( 55 | OPENAI_API_KEY=os.getenv('OPENAI_API_KEY'), 56 | GEMINI_API_KEY=os.getenv('GEMINI_API_KEY'), 57 | DEFAULT_AI_SERVICE=os.getenv('DEFAULT_AI_SERVICE', 'auto'), 58 | DATABASE=os.path.join(app.instance_path, 'chat_history.sqlite'), 59 | ) 60 | 61 | # Ensure the instance folder exists 62 | try: 63 | os.makedirs(app.instance_path) 64 | except OSError: 65 | pass 66 | 67 | # Initialize services 68 | chat_history = ChatHistory(app.config['DATABASE']) 69 | api_keys = { 70 | "OPENAI_API_KEY": app.config['OPENAI_API_KEY'], 71 | "GEMINI_API_KEY": app.config['GEMINI_API_KEY'] 72 | } 73 | feedback_system = CodeFeedbackSystem(api_keys) 74 | 75 | @app.route('/') 76 | def home(): 77 | """Home page route with conversation history""" 78 | histories = chat_history.get_all_conversations() 79 | return render_template('chat.html', histories=histories) 80 | 81 | @app.route('/analyze', methods=['POST']) 82 | def analyze(): 83 | """Analyze code from GitHub URL""" 84 | try: 85 | data = request.json 86 | github_url = data['url'] 87 | 88 | # Start new conversation 89 | conversation_id = chat_history.start_conversation(github_url) 90 | 91 | # Analyze code 92 | response = feedback_system.analyze_code(github_url) 93 | 94 | # Save initial analysis 95 | chat_history.add_message(conversation_id, "Analyze code", response) 96 | 97 | return jsonify({ 98 | 'conversation_id': conversation_id, 99 | 'response': response 100 | }) 101 | 102 | except Exception as e: 103 | return jsonify({ 104 | 'error': str(e) 105 | }), 400 106 | 107 | @app.route('/chat', methods=['POST']) 108 | def chat(): 109 | """Handle chat messages""" 110 | try: 111 | data = request.json 112 | message = data['message'] 113 | conversation_id = data['conversation_id'] 114 | 115 | # Get conversation history 116 | history = chat_history.get_conversation_history(conversation_id) 117 | github_url = history['github_url'] 118 | 119 | # Get code context 120 | code_context = feedback_system.github_handler.get_file_content(github_url) 121 | 122 | if github_url.endswith('.ipynb'): 123 | code_context = feedback_system.notebook_handler.extract_notebook_code(code_context) 124 | 125 | # Get AI response 126 | response = feedback_system.chat_about_code(message, code_context) 127 | 128 | # Save message and response 129 | chat_history.add_message(conversation_id, message, response) 130 | 131 | return jsonify({'response': response}) 132 | 133 | except Exception as e: 134 | return jsonify({ 135 | 'error': str(e) 136 | }), 400 137 | 138 | @app.route('/history/') 139 | def get_history(conversation_id): 140 | """Get chat history for a conversation""" 141 | try: 142 | history = chat_history.get_conversation_history(conversation_id) 143 | histories = chat_history.get_all_conversations() 144 | return render_template('chat.html', 145 | history=history, 146 | histories=histories, 147 | conversation_id=conversation_id) 148 | except Exception as e: 149 | return str(e), 400 150 | 151 | @app.errorhandler(404) 152 | def not_found_error(error): 153 | """Handle 404 errors""" 154 | histories = chat_history.get_all_conversations() 155 | return render_template('chat.html', 156 | error="Page not found", 157 | histories=histories), 404 158 | 159 | @app.errorhandler(500) 160 | def internal_error(error): 161 | """Handle 500 errors""" 162 | histories = chat_history.get_all_conversations() 163 | return render_template('chat.html', 164 | error="Internal server error", 165 | histories=histories), 500 166 | 167 | return app 168 | 169 | def init_db(): 170 | """Initialize the database""" 171 | with app.app_context(): 172 | db_path = app.config['DATABASE'] 173 | chat_history = ChatHistory(db_path) 174 | chat_history.init_db() 175 | 176 | app = create_app() 177 | 178 | if __name__ == '__main__': 179 | # Configuration for running the app 180 | port = int(os.getenv('PORT', 5000)) 181 | debug = os.getenv('FLASK_ENV') == 'development' 182 | 183 | # Initialize database 184 | init_db() 185 | 186 | # Run the app 187 | app.run( 188 | host='0.0.0.0', 189 | port=port, 190 | debug=debug 191 | ) -------------------------------------------------------------------------------- /database/chat_history.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from datetime import datetime 3 | 4 | class ChatHistory: 5 | def __init__(self, db_path="chat_history.db"): 6 | """Initialize chat history with database path""" 7 | self.db_path = db_path 8 | self.init_db() 9 | 10 | def init_db(self): 11 | """Initialize database tables""" 12 | conn = sqlite3.connect(self.db_path) 13 | c = conn.cursor() 14 | 15 | # Conversations table 16 | c.execute('''CREATE TABLE IF NOT EXISTS conversations 17 | (id INTEGER PRIMARY KEY AUTOINCREMENT, 18 | github_url TEXT NOT NULL, 19 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)''') 20 | 21 | # Messages table 22 | c.execute('''CREATE TABLE IF NOT EXISTS messages 23 | (id INTEGER PRIMARY KEY AUTOINCREMENT, 24 | conversation_id INTEGER NOT NULL, 25 | message TEXT NOT NULL, 26 | response TEXT NOT NULL, 27 | timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 28 | FOREIGN KEY (conversation_id) REFERENCES conversations (id))''') 29 | 30 | conn.commit() 31 | conn.close() 32 | 33 | def start_conversation(self, github_url): 34 | """Start a new conversation and return its ID""" 35 | conn = sqlite3.connect(self.db_path) 36 | c = conn.cursor() 37 | 38 | try: 39 | c.execute("""INSERT INTO conversations (github_url, created_at) 40 | VALUES (?, ?)""", (github_url, datetime.now())) 41 | conversation_id = c.lastrowid 42 | conn.commit() 43 | return conversation_id 44 | finally: 45 | conn.close() 46 | 47 | def add_message(self, conversation_id, message, response): 48 | """Add a message and its response to a conversation""" 49 | conn = sqlite3.connect(self.db_path) 50 | c = conn.cursor() 51 | 52 | try: 53 | c.execute("""INSERT INTO messages (conversation_id, message, response, timestamp) 54 | VALUES (?, ?, ?, ?)""", 55 | (conversation_id, message, response, datetime.now())) 56 | conn.commit() 57 | finally: 58 | conn.close() 59 | 60 | def get_conversation_history(self, conversation_id): 61 | """Get the full history of a conversation""" 62 | conn = sqlite3.connect(self.db_path) 63 | conn.row_factory = sqlite3.Row 64 | c = conn.cursor() 65 | 66 | try: 67 | # Get conversation details 68 | c.execute("""SELECT id, github_url, created_at 69 | FROM conversations 70 | WHERE id = ?""", (conversation_id,)) 71 | conversation = c.fetchone() 72 | 73 | if not conversation: 74 | raise ValueError(f"Conversation with ID {conversation_id} not found") 75 | 76 | # Get messages 77 | c.execute("""SELECT message, response, timestamp 78 | FROM messages 79 | WHERE conversation_id = ? 80 | ORDER BY timestamp""", (conversation_id,)) 81 | messages = c.fetchall() 82 | 83 | return { 84 | 'id': conversation['id'], 85 | 'github_url': conversation['github_url'], 86 | 'created_at': datetime.strptime(conversation['created_at'], '%Y-%m-%d %H:%M:%S.%f'), 87 | 'messages': [ 88 | { 89 | 'message': msg['message'], 90 | 'response': msg['response'], 91 | 'timestamp': datetime.strptime(msg['timestamp'], '%Y-%m-%d %H:%M:%S.%f') 92 | } for msg in messages 93 | ] 94 | } 95 | finally: 96 | conn.close() 97 | 98 | def get_all_conversations(self): 99 | """Get all conversations with their basic info""" 100 | conn = sqlite3.connect(self.db_path) 101 | conn.row_factory = sqlite3.Row 102 | c = conn.cursor() 103 | 104 | try: 105 | c.execute("""SELECT id, github_url, created_at 106 | FROM conversations 107 | ORDER BY created_at DESC""") 108 | 109 | conversations = [ 110 | { 111 | 'id': row['id'], 112 | 'github_url': row['github_url'], 113 | 'created_at': datetime.strptime(row['created_at'], '%Y-%m-%d %H:%M:%S.%f') 114 | } 115 | for row in c.fetchall() 116 | ] 117 | 118 | return conversations 119 | finally: 120 | conn.close() 121 | 122 | def delete_conversation(self, conversation_id): 123 | """Delete a conversation and all its messages""" 124 | conn = sqlite3.connect(self.db_path) 125 | c = conn.cursor() 126 | 127 | try: 128 | # Delete messages first (due to foreign key constraint) 129 | c.execute("DELETE FROM messages WHERE conversation_id = ?", (conversation_id,)) 130 | 131 | # Then delete the conversation 132 | c.execute("DELETE FROM conversations WHERE id = ?", (conversation_id,)) 133 | 134 | conn.commit() 135 | return True 136 | except Exception as e: 137 | print(f"Error deleting conversation: {e}") 138 | return False 139 | finally: 140 | conn.close() 141 | 142 | def clear_all_history(self): 143 | """Clear all conversations and messages""" 144 | conn = sqlite3.connect(self.db_path) 145 | c = conn.cursor() 146 | 147 | try: 148 | # Delete all messages first (due to foreign key constraint) 149 | c.execute("DELETE FROM messages") 150 | 151 | # Then delete all conversations 152 | c.execute("DELETE FROM conversations") 153 | 154 | conn.commit() 155 | return True 156 | except Exception as e: 157 | print(f"Error clearing history: {e}") 158 | return False 159 | finally: 160 | conn.close() 161 | 162 | def get_conversation_stats(self, conversation_id): 163 | """Get statistics for a conversation""" 164 | conn = sqlite3.connect(self.db_path) 165 | c = conn.cursor() 166 | 167 | try: 168 | c.execute("""SELECT 169 | COUNT(*) as message_count, 170 | MIN(timestamp) as first_message, 171 | MAX(timestamp) as last_message 172 | FROM messages 173 | WHERE conversation_id = ?""", (conversation_id,)) 174 | 175 | stats = c.fetchone() 176 | return { 177 | 'message_count': stats[0], 178 | 'first_message': datetime.strptime(stats[1], '%Y-%m-%d %H:%M:%S.%f') if stats[1] else None, 179 | 'last_message': datetime.strptime(stats[2], '%Y-%m-%d %H:%M:%S.%f') if stats[2] else None 180 | } 181 | finally: 182 | conn.close() -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code Feedback AI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 98 | 99 | 100 |
101 | 102 |
103 |
104 |
105 |

106 | {% if conversation_id %} 107 | Code Analysis 108 | {% else %} 109 | New Analysis 110 | {% endif %} 111 |

112 | {% if conversation_id %} 113 |
114 | 115 | Analyzing: {{ history.github_url.split('/')[-1] if history else '' }} 116 | 117 |
118 | {% endif %} 119 |
120 |
121 |
122 | 123 | 124 |
125 | {% block content %}{% endblock %} 126 |
127 |
128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /utils/notebook_handler.py: -------------------------------------------------------------------------------- 1 | import nbformat 2 | import re 3 | 4 | class NotebookHandler: 5 | def __init__(self): 6 | """Initialize NotebookHandler""" 7 | self.known_imports = set([ 8 | 'pandas', 'numpy', 'matplotlib', 'seaborn', 'sklearn', 'tensorflow', 9 | 'torch', 'keras', 'scipy', 'requests', 'beautifulsoup4', 'flask', 10 | 'django', 'sqlalchemy', 'pytest', 'unittest', 'os', 'sys', 'json', 11 | 'csv', 'random', 'datetime', 'time', 'math', 're', 'collections' 12 | ]) 13 | 14 | def extract_notebook_code(self, notebook_content): 15 | """Extract code and markdown content from notebook""" 16 | try: 17 | notebook = nbformat.reads(notebook_content, as_version=4) 18 | code_content = [] 19 | markdown_content = [] 20 | imports = set() 21 | 22 | current_section = None 23 | section_content = [] 24 | 25 | for cell in notebook.cells: 26 | # Markdown hücrelerini işle 27 | if cell.cell_type == "markdown": 28 | markdown_text = cell.source.strip() 29 | if markdown_text: 30 | # Yeni bir bölüm başlığı mı kontrol et 31 | header_match = re.match(r'^#+ (.+)', markdown_text) 32 | if header_match: 33 | # Önceki bölümü kaydet 34 | if current_section and section_content: 35 | markdown_content.append({ 36 | 'section': current_section, 37 | 'content': '\n'.join(section_content) 38 | }) 39 | # Yeni bölümü başlat 40 | current_section = header_match.group(1).strip() 41 | section_content = [markdown_text] 42 | else: 43 | if current_section: 44 | section_content.append(markdown_text) 45 | else: 46 | markdown_content.append({ 47 | 'section': 'General', 48 | 'content': markdown_text 49 | }) 50 | 51 | # Kod hücrelerini işle 52 | elif cell.cell_type == "code": 53 | code_text = cell.source.strip() 54 | if code_text: 55 | # Import ifadelerini tespit et 56 | imports.update(self._extract_imports(code_text)) 57 | 58 | # Hücre çıktılarını kontrol et 59 | outputs = [] 60 | if hasattr(cell, 'outputs'): 61 | for output in cell.outputs: 62 | if output.get('output_type') == 'error': 63 | # Hata varsa kaydet 64 | outputs.append({ 65 | 'type': 'error', 66 | 'content': output.get('traceback', [''])[0] 67 | }) 68 | elif output.get('output_type') == 'stream': 69 | # Print çıktılarını kaydet 70 | outputs.append({ 71 | 'type': 'stream', 72 | 'content': output.get('text', '') 73 | }) 74 | 75 | code_content.append({ 76 | 'code': code_text, 77 | 'outputs': outputs 78 | }) 79 | 80 | # Son bölümü ekle 81 | if current_section and section_content: 82 | markdown_content.append({ 83 | 'section': current_section, 84 | 'content': '\n'.join(section_content) 85 | }) 86 | 87 | # Markdown içeriğini analiz et 88 | documentation = self._analyze_markdown_content(markdown_content) 89 | 90 | return { 91 | 'code': '\n\n'.join(cell['code'] for cell in code_content), 92 | 'code_cells': code_content, 93 | 'markdown_content': markdown_content, 94 | 'documentation': documentation, 95 | 'imports': list(imports), 96 | 'has_errors': any(any(output['type'] == 'error' for output in cell['outputs']) 97 | for cell in code_content), 98 | 'cell_count': { 99 | 'code': len(code_content), 100 | 'markdown': len(markdown_content) 101 | } 102 | } 103 | 104 | except Exception as e: 105 | raise Exception(f"Notebook içeriği işlenirken hata oluştu: {str(e)}") 106 | 107 | def _extract_imports(self, code): 108 | """Extract imported libraries from code""" 109 | imports = set() 110 | 111 | # import x pattern 112 | import_matches = re.findall(r'import\s+(\w+)', code) 113 | imports.update(import_matches) 114 | 115 | # from x import y pattern 116 | from_matches = re.findall(r'from\s+(\w+)\s+import', code) 117 | imports.update(from_matches) 118 | 119 | # Sadece bilinen kütüphaneleri döndür 120 | return {imp for imp in imports if imp in self.known_imports} 121 | 122 | def _analyze_markdown_content(self, markdown_cells): 123 | """Analyze markdown content for documentation""" 124 | documentation = { 125 | 'project_description': '', 126 | 'setup_instructions': '', 127 | 'usage_examples': [], 128 | 'parameters': [], 129 | 'requirements': [], 130 | 'notes': [], 131 | 'references': [] 132 | } 133 | 134 | for cell in markdown_cells: 135 | content = cell['content'].lower() 136 | original_content = cell['content'] 137 | 138 | # Proje açıklaması 139 | if any(keyword in content for keyword in ['overview', 'introduction', 'description', 'about']): 140 | documentation['project_description'] = original_content 141 | 142 | # Kurulum talimatları 143 | elif any(keyword in content for keyword in ['setup', 'installation', 'getting started']): 144 | documentation['setup_instructions'] = original_content 145 | 146 | # Kullanım örnekleri 147 | elif any(keyword in content for keyword in ['usage', 'example', 'how to']): 148 | documentation['usage_examples'].append(original_content) 149 | 150 | # Parametreler 151 | elif any(keyword in content for keyword in ['parameter', 'argument', 'config']): 152 | documentation['parameters'].append(original_content) 153 | 154 | # Gereksinimler 155 | elif any(keyword in content for keyword in ['requirement', 'dependency', 'prerequisites']): 156 | documentation['requirements'].append(original_content) 157 | 158 | # Notlar 159 | elif any(keyword in content for keyword in ['note', 'warning', 'important', 'attention']): 160 | documentation['notes'].append(original_content) 161 | 162 | # Referanslar 163 | elif any(keyword in content for keyword in ['reference', 'citation', 'source']): 164 | documentation['references'].append(original_content) 165 | 166 | return documentation 167 | 168 | def get_code_summary(self, code_cells): 169 | """Generate a summary of the code content""" 170 | summary = { 171 | 'total_lines': 0, 172 | 'has_functions': False, 173 | 'has_classes': False, 174 | 'complexity_indicators': { 175 | 'nested_loops': 0, 176 | 'conditional_statements': 0, 177 | 'function_definitions': 0, 178 | 'class_definitions': 0 179 | } 180 | } 181 | 182 | for cell in code_cells: 183 | code = cell['code'] 184 | # Satır sayısı 185 | summary['total_lines'] += len(code.split('\n')) 186 | 187 | # Fonksiyon ve sınıf kontrolü 188 | if 'def ' in code: 189 | summary['has_functions'] = True 190 | summary['complexity_indicators']['function_definitions'] += len(re.findall(r'\bdef\s+\w+', code)) 191 | 192 | if 'class ' in code: 193 | summary['has_classes'] = True 194 | summary['complexity_indicators']['class_definitions'] += len(re.findall(r'\bclass\s+\w+', code)) 195 | 196 | # Karmaşıklık göstergeleri 197 | summary['complexity_indicators']['nested_loops'] += len(re.findall(r'\bfor\b.*\bfor\b|\bwhile\b.*\bwhile\b', code)) 198 | summary['complexity_indicators']['conditional_statements'] += len(re.findall(r'\bif\b|\belif\b|\belse\b', code)) 199 | 200 | return summary 201 | 202 | def is_notebook_organized(self, markdown_cells, code_cells): 203 | """Check if the notebook is well-organized""" 204 | return { 205 | 'has_introduction': any('introduction' in cell['content'].lower() for cell in markdown_cells), 206 | 'has_sections': any(cell['section'] != 'General' for cell in markdown_cells), 207 | 'has_documentation': len(markdown_cells) > 0, 208 | 'code_markdown_ratio': len(code_cells) / (len(markdown_cells) + 1), # +1 to avoid division by zero 209 | 'avg_code_cell_length': sum(len(cell['code'].split('\n')) for cell in code_cells) / (len(code_cells) + 1) 210 | } -------------------------------------------------------------------------------- /analyzers/code_analyzer.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | import google.generativeai as genai 3 | import json 4 | import re 5 | from config.settings import ( 6 | SYSTEM_PROMPT, 7 | OPENAI_MODEL, 8 | TEMPERATURE, 9 | ANALYSIS_TEMPLATE, 10 | DEFAULT_AI_SERVICE 11 | ) 12 | class AIServiceFactory: 13 | @staticmethod 14 | def get_service(api_keys): 15 | """Returns the appropriate AI service based on available API keys""" 16 | if api_keys.get("OPENAI_API_KEY"): 17 | return "openai" 18 | elif api_keys.get("GEMINI_API_KEY"): 19 | return "gemini" 20 | else: 21 | raise ValueError("No valid API key found for any AI service") 22 | 23 | class CodeAnalyzer: 24 | def __init__(self, api_keys): 25 | """Initialize with API keys""" 26 | self.api_keys = api_keys 27 | self.service = AIServiceFactory.get_service(api_keys) 28 | self.ANALYSIS_TEMPLATE = ANALYSIS_TEMPLATE 29 | 30 | if self.service == "openai": 31 | self.client = OpenAI(api_key=api_keys["OPENAI_API_KEY"]) 32 | elif self.service == "gemini": 33 | genai.configure(api_key=api_keys["GEMINI_API_KEY"]) 34 | self.client = genai.GenerativeModel('gemini-pro') 35 | 36 | def analyze_code(self, code, notebook_data=None): 37 | """Main analysis function""" 38 | try: 39 | if self.service == "openai": 40 | analysis = self._analyze_with_openai(code, notebook_data) 41 | else: 42 | analysis = self._analyze_with_gemini(code, notebook_data) 43 | 44 | # Validate and clean the analysis results 45 | analysis = self._validate_and_clean_analysis(analysis) 46 | return analysis 47 | 48 | except Exception as e: 49 | print(f"Analysis error: {str(e)}") 50 | return self.ANALYSIS_TEMPLATE 51 | 52 | def _analyze_with_openai(self, code, notebook_data=None): 53 | """Analyze code using OpenAI's GPT-4""" 54 | try: 55 | # Prepare context with code and documentation if available 56 | context = code 57 | if notebook_data: 58 | markdown_sections = [] 59 | if notebook_data.get('documentation'): 60 | doc = notebook_data['documentation'] 61 | if doc['project_description']: 62 | markdown_sections.append(f"# Project Description\n{doc['project_description']}") 63 | if doc['usage_examples']: 64 | markdown_sections.append(f"# Usage Examples\n{' '.join(doc['usage_examples'])}") 65 | if doc['parameters']: 66 | markdown_sections.append(f"# Parameters\n{' '.join(doc['parameters'])}") 67 | if doc['notes']: 68 | markdown_sections.append(f"# Notes\n{' '.join(doc['notes'])}") 69 | 70 | if markdown_sections: 71 | context = f"""# Documentation 72 | {' '.join(markdown_sections)} 73 | 74 | # Code 75 | {code}""" 76 | 77 | prompt = f"""Lütfen aşağıdaki Python kodunu detaylı olarak analiz et. 78 | {' Kod ile birlikte verilen dokümantasyonu da dikkate al.' if notebook_data else ''} 79 | Yanıtını kesinlikle belirtilen JSON formatında ver ve hiçbir ek açıklama ekleme. 80 | Her bölüm için detaylı ve yapıcı geri bildirimler sağla. 81 | 82 | {SYSTEM_PROMPT} 83 | 84 | Analiz edilecek içerik: 85 | {context}""" 86 | 87 | response = self.client.chat.completions.create( 88 | model=OPENAI_MODEL, 89 | messages=[ 90 | {"role": "system", "content": "Sen deneyimli bir Python kod analisti ve geliştiricisisin."}, 91 | {"role": "user", "content": prompt} 92 | ], 93 | temperature=0.3, 94 | response_format={"type": "json_object"} 95 | ) 96 | 97 | content = response.choices[0].message.content.strip() 98 | return json.loads(content) 99 | 100 | except Exception as e: 101 | print(f"OpenAI API error: {str(e)}") 102 | return self.ANALYSIS_TEMPLATE 103 | 104 | def _analyze_with_gemini(self, code, notebook_data=None): 105 | """Analyze code using Google's Gemini""" 106 | try: 107 | # Prepare context with code and documentation if available 108 | context = code 109 | if notebook_data: 110 | markdown_sections = [] 111 | if notebook_data.get('documentation'): 112 | doc = notebook_data['documentation'] 113 | if doc['project_description']: 114 | markdown_sections.append(f"# Project Description\n{doc['project_description']}") 115 | if doc['usage_examples']: 116 | markdown_sections.append(f"# Usage Examples\n{' '.join(doc['usage_examples'])}") 117 | if doc['parameters']: 118 | markdown_sections.append(f"# Parameters\n{' '.join(doc['parameters'])}") 119 | if doc['notes']: 120 | markdown_sections.append(f"# Notes\n{' '.join(doc['notes'])}") 121 | 122 | if markdown_sections: 123 | context = f"""# Documentation 124 | {' '.join(markdown_sections)} 125 | 126 | # Code 127 | {code}""" 128 | 129 | prompt = f"""Lütfen aşağıdaki Python kodunu analiz et ve sonucu TAM OLARAK aşağıdaki JSON formatında döndür: 130 | 131 | {SYSTEM_PROMPT} 132 | 133 | Analiz edilecek içerik: 134 | {context} 135 | 136 | Önemli kurallar: 137 | 1. Yanıt MUTLAKA geçerli bir JSON olmalı 138 | 2. Tüm alanlar doldurulmalı, boş bırakılmamalı 139 | 3. Dokümantasyondaki bilgiler de dikkate alınmalı 140 | 4. Her kod örneği için detaylı açıklama eklenmeli 141 | 5. Her bölüm için kapsamlı analiz yapılmalı 142 | 6. Yanıtta JSON dışında hiçbir ek metin olmamalı""" 143 | 144 | response = self.client.generate_content(prompt) 145 | content = response.text.strip() 146 | 147 | json_start = content.find('{') 148 | json_end = content.rfind('}') + 1 149 | 150 | if json_start >= 0 and json_end > json_start: 151 | content = content[json_start:json_end] 152 | 153 | try: 154 | analysis = json.loads(content) 155 | return self._fix_gemini_output(analysis) 156 | except json.JSONDecodeError: 157 | return self._extract_analysis_from_text(content) 158 | 159 | except Exception as e: 160 | print(f"Gemini API error: {str(e)}") 161 | return self.ANALYSIS_TEMPLATE 162 | 163 | def _fix_gemini_output(self, analysis): 164 | """Fix and validate Gemini output format""" 165 | if 'kod_ornekleri' in analysis: 166 | if isinstance(analysis['kod_ornekleri'], list): 167 | fixed_examples = [] 168 | for example in analysis['kod_ornekleri']: 169 | if isinstance(example, dict) and 'aciklama' in example and 'kod' in example: 170 | fixed_examples.append(example) 171 | elif isinstance(example, str): 172 | fixed_examples.append({ 173 | "aciklama": "Önerilen kod iyileştirmesi", 174 | "kod": example 175 | }) 176 | analysis['kod_ornekleri'] = fixed_examples 177 | else: 178 | analysis['kod_ornekleri'] = [{ 179 | "aciklama": "Önerilen kod iyileştirmesi", 180 | "kod": "# Örnek kod hazırlanıyor" 181 | }] 182 | 183 | # Liste olması gereken alanları kontrol et 184 | list_fields = ['kullanilan_teknolojiler', 'guclu_yonler', 'iyilestirme_alanlari', 185 | 'guvenlik_onerileri', 'performans_onerileri'] 186 | 187 | for field in list_fields: 188 | if field in analysis and not isinstance(analysis[field], list): 189 | analysis[field] = [str(analysis[field])] 190 | elif field not in analysis: 191 | analysis[field] = ["Analiz tamamlanıyor..."] 192 | 193 | return analysis 194 | 195 | def _validate_and_clean_analysis(self, analysis): 196 | """Validate and clean the analysis results""" 197 | required_fields = [ 198 | "proje_amaci", "proje_ozeti", "kullanilan_teknolojiler", 199 | "genel_degerlendirme", "guclu_yonler", "iyilestirme_alanlari", 200 | "kod_ornekleri", "guvenlik_onerileri", "performans_onerileri" 201 | ] 202 | 203 | for field in required_fields: 204 | if field not in analysis or not analysis[field]: 205 | if field in ["proje_amaci", "proje_ozeti", "genel_degerlendirme"]: 206 | analysis[field] = "Analiz tamamlanıyor..." 207 | elif field == "kod_ornekleri": 208 | analysis[field] = [{ 209 | "aciklama": "Kod önerisi hazırlanıyor...", 210 | "kod": "# Örnek kod hazırlanıyor" 211 | }] 212 | else: 213 | analysis[field] = ["Analiz tamamlanıyor..."] 214 | 215 | return analysis 216 | 217 | def _extract_analysis_from_text(self, text): 218 | """Extract structured analysis from unstructured text""" 219 | try: 220 | # Try to find and parse JSON-like content 221 | json_match = re.search(r'\{[\s\S]*\}', text) 222 | if json_match: 223 | try: 224 | analysis = json.loads(json_match.group(0)) 225 | return self._fix_gemini_output(analysis) 226 | except: 227 | pass 228 | 229 | # Extract content using patterns 230 | analysis = {} 231 | 232 | # Extract main sections 233 | sections = { 234 | 'proje_amaci': r'"proje_amaci":\s*"([^"]*)"', 235 | 'proje_ozeti': r'"proje_ozeti":\s*"([^"]*)"', 236 | 'genel_degerlendirme': r'"genel_degerlendirme":\s*"([^"]*)"' 237 | } 238 | 239 | for key, pattern in sections.items(): 240 | match = re.search(pattern, text) 241 | analysis[key] = match.group(1) if match else "Analiz tamamlanıyor..." 242 | 243 | # Extract list sections 244 | list_sections = { 245 | 'kullanilan_teknolojiler': r'"kullanilan_teknolojiler":\s*\[(.*?)\]', 246 | 'guclu_yonler': r'"guclu_yonler":\s*\[(.*?)\]', 247 | 'iyilestirme_alanlari': r'"iyilestirme_alanlari":\s*\[(.*?)\]', 248 | 'guvenlik_onerileri': r'"guvenlik_onerileri":\s*\[(.*?)\]', 249 | 'performans_onerileri': r'"performans_onerileri":\s*\[(.*?)\]' 250 | } 251 | 252 | for key, pattern in list_sections.items(): 253 | matches = re.findall(pattern, text, re.DOTALL) 254 | if matches: 255 | items = re.findall(r'"([^"]*)"', matches[0]) 256 | analysis[key] = items if items else ["Analiz tamamlanıyor..."] 257 | else: 258 | analysis[key] = ["Analiz tamamlanıyor..."] 259 | 260 | # Extract code examples 261 | code_pattern = r'```python\s*(.*?)\s*```' 262 | code_blocks = list(re.finditer(code_pattern, text, re.DOTALL)) 263 | examples = [] 264 | 265 | for block in code_blocks: 266 | code = block.group(1).strip() 267 | if code: 268 | examples.append({ 269 | "aciklama": "Önerilen kod iyileştirmesi", 270 | "kod": code 271 | }) 272 | 273 | analysis["kod_ornekleri"] = examples if examples else [{ 274 | "aciklama": "Kod önerisi hazırlanıyor...", 275 | "kod": "# Örnek kod hazırlanıyor" 276 | }] 277 | 278 | return analysis 279 | 280 | except Exception as e: 281 | print(f"Text extraction error: {str(e)}") 282 | return self.ANALYSIS_TEMPLATE 283 | 284 | def chat_about_code(self, message, code_context): 285 | """Chat about code using the selected AI service""" 286 | try: 287 | if self.service == "openai": 288 | return self._chat_with_openai(message, code_context) 289 | else: 290 | return self._chat_with_gemini(message, code_context) 291 | except Exception as e: 292 | return f"Chat error: {str(e)}" 293 | 294 | def _chat_with_openai(self, message, code_context): 295 | """Chat using OpenAI's GPT-4""" 296 | response = self.client.chat.completions.create( 297 | model=OPENAI_MODEL, 298 | messages=[ 299 | { 300 | "role": "system", 301 | "content": """Sen deneyimli bir Python geliştiricisin. 302 | Kullanıcının sorularına net, açıklayıcı ve yapıcı yanıtlar ver. 303 | Kod örnekleri verirken açıklamalarını da ekle.""" 304 | }, 305 | { 306 | "role": "user", 307 | "content": f"Kod:\n{code_context}\n\nSoru: {message}" 308 | } 309 | ], 310 | #temperature=0.5 311 | ) 312 | return response.choices[0].message.content 313 | 314 | def _chat_with_gemini(self, message, code_context): 315 | """Chat using Google's Gemini""" 316 | prompt = f"""Sen deneyimli bir Python geliştiricisin. 317 | Kullanıcının sorularına net, açıklayıcı ve yapıcı yanıtlar ver. 318 | Kod örnekleri verirken açıklamalarını da ekle. 319 | 320 | Kod: 321 | {code_context} 322 | 323 | Soru: {message}""" 324 | 325 | response = self.client.generate_content(prompt) 326 | return response.text --------------------------------------------------------------------------------