File size: 7,330 Bytes
a496aae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
055f523
a496aae
 
 
 
 
 
 
 
 
 
 
 
 
 
4cc7f3b
3cc91c2
3fc2089
3cc91c2
4cc7f3b
a496aae
3cc91c2
 
 
 
4cc7f3b
3cc91c2
 
 
 
 
5575d19
4cc7f3b
 
 
3cc91c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a722b8e
 
3cc91c2
4cc7f3b
3cc91c2
 
 
4cc7f3b
3cc91c2
 
 
 
 
4cc7f3b
 
 
3cc91c2
 
 
 
 
 
a496aae
 
 
5575d19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a496aae
 
 
 
 
 
 
 
 
 
 
5575d19
a496aae
 
 
055f523
a496aae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import google.generativeai as genai
import os
from typing import List, Dict
import logging
from dotenv import load_dotenv

load_dotenv()
logger = logging.getLogger(__name__)

class ChatService:
    def __init__(self):
        api_key = os.getenv("GEMINI_API_KEY")
        if not api_key:
            logger.warning("⚠️ GEMINI_API_KEY not found - chat will use fallback responses")
            self.model = None
            self.gemini_available = False
        else:
            try:
                genai.configure(api_key=api_key)
                self.model = genai.GenerativeModel('gemini-2.5-flash')
                self.gemini_available = True
                logger.info("🤖 Gemini chat service initialized")
            except Exception as e:
                logger.error(f"❌ Failed to initialize Gemini: {e}")
                self.model = None
                self.gemini_available = False
    
    async def generate_response(self, query: str, code_chunks: List[Dict], repository_name: str) -> Dict:
        if not self.gemini_available:
            return self.generate_fallback_response(query, code_chunks, repository_name)
        
        try:
            context = self.prepare_context(code_chunks)
            
            prompt = f"""You are an expert code assistant analyzing the {repository_name} repository.

User Question: {query}

Code Context:
{context}

RESPONSE STRUCTURE:

1. ALWAYS start with:
## Main Answer
[Provide a direct, concise answer to the user's question first]

2. Then adapt the rest based on query type:

FOR CODE EXPLANATION/ARCHITECTURE QUERIES:
## Implementation Overview
### Key Components
### Technical Details
### How It Works

FOR DEBUGGING/ERROR QUERIES:
## Root Cause Analysis
## Affected Code
## Suggested Solution
## Prevention Tips

FOR DOCUMENTATION/README REQUESTS:
## Overview
## Installation
## Usage
## Configuration
[Standard README sections as appropriate]

FOR "HOW TO" QUERIES:
## Step-by-Step Guide
## Code Examples
## Best Practices

FOR FEATURE REQUESTS/SUGGESTIONS:
## Current Implementation
## Proposed Approach
## Implementation Steps

**Important**: These are examples, not rigid templates. Use your judgment to structure the response in the way that best answers the user's specific question. You may combine elements from multiple patterns, create your own sections, or use entirely different headings if more appropriate.

Always end with:
## Additional Notes
[Limitations, missing information, or recommendations if relevant]

UNIVERSAL FORMATTING RULES (MANDATORY):
- NO emojis - use clean text only
- Use **bold** for important file names and concepts
- Use `backticks` for functions, variables, classes, and code snippets
- ALWAYS reference specific files and line numbers: `filename.py` (lines X-Y)
- Use > blockquotes for important insights or warnings
- Use proper markdown hierarchy (##, ###, -, >, etc.)
- Be comprehensive and detailed
- Explain both WHAT the code does and HOW it works
- Professional documentation style
- Focus on explanation rather than just code listing
- Clean, professional markdown formatting (GitHub README style)

CRITICAL: Every code reference MUST include the file path and line numbers from the context provided above.

Your detailed markdown response:"""
            
            response = self.model.generate_content(prompt)
            
            # --- START: BEST FIX ---
            # Clean the response text to remove markdown code block wrappers
            response_text = response.text
            
            if response_text.startswith("```markdown"):
                response_text = response_text[len("```markdown"):]
            elif response_text.startswith("```"):
                response_text = response_text[len("```"):]
                
            if response_text.endswith("```"):
                response_text = response_text[:-len("```")]
                
            response_text = response_text.strip() # Remove any leading/trailing whitespace
            # --- END: BEST FIX ---

            sources = []
            for chunk in code_chunks:
                sources.append({
                    'file_path': chunk['file_path'],
                    'start_line': chunk['start_line'],
                    'end_line': chunk['end_line'],
                    'similarity': round(chunk['similarity'], 3),
                    'preview': chunk['content'][:200] + "..."
                })
            
            return {
                'response': response_text,  # Use the cleaned text
                'sources': sources,
                'context_chunks_used': len(code_chunks),
                'repository_name': repository_name,
                'model_used': 'gemini-2.5-flash',
                'success': True
            }
            
        except Exception as e:
            logger.error(f"❌ Gemini error: {e}")
            if "429" in str(e) or "quota" in str(e).lower():
                return self.generate_quota_response(query, code_chunks, repository_name)
            return self.generate_fallback_response(query, code_chunks, repository_name)
    
    def prepare_context(self, code_chunks: List[Dict]) -> str:
        context_sections = []
        for i, chunk in enumerate(code_chunks, 1):
            context_sections.append(f"""
Code Reference {i}:
File: {chunk['file_path']}
Lines: {chunk['start_line']}-{chunk['end_line']}
Similarity: {chunk['similarity']:.2f}
{chunk['content']}
""")
        return "\n".join(context_sections)
    
    def generate_quota_response(self, query: str, code_chunks: List[Dict], repository_name: str) -> Dict:
        context = self.prepare_context(code_chunks)
        response = f"""🚫 Gemini quota exceeded, but I found {len(code_chunks)} relevant code sections:

{context}

The search found relevant code with similarity scores from {min(c['similarity'] for c in code_chunks):.2f} to {max(c['similarity'] for c in code_chunks):.2f}. Please try again in a few minutes when quota resets."""
        
        return self.create_response_dict(response, code_chunks, repository_name, 'quota_exceeded')
    
    def generate_fallback_response(self, query: str, code_chunks: List[Dict], repository_name: str) -> Dict:
        context = self.prepare_context(code_chunks)
        response = f"""Found {len(code_chunks)} relevant code sections for: "{query}"

{context}

Note: AI analysis requires API configuration. The search results above show the most relevant code."""
        
        return self.create_response_dict(response, code_chunks, repository_name, 'fallback')
    
    def create_response_dict(self, response: str, code_chunks: List[Dict], repository_name: str, model_used: str) -> Dict:
        sources = []
        for chunk in code_chunks:
            sources.append({
                'file_path': chunk['file_path'],
                'start_line': chunk['start_line'],
                'end_line': chunk['end_line'],
                'similarity': round(chunk['similarity'], 3),
                'preview': chunk['content'][:200] + "..."
            })
        
        return {
            'response': response,
            'sources': sources,
            'context_chunks_used': len(code_chunks),
            'repository_name': repository_name,
            'model_used': model_used,
            'success': True
        }