MingZ6 commited on
Commit
b52dd21
Β·
1 Parent(s): 0f46319

init project

Browse files
PromptTemplate.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "prompt_template": "You are an expert repository analyst and technical educator, skilled at explaining software repository setups and configurations.\n\n# Task Description\nGenerate 3 multiple-choice questions about the given repository with 4 options each. Include explanations for the correct answers.\n\n# Output Format\n- Q1: Question 1\n- A1: Option 1\n- A2: Option 2\n- A3: Option 3\n- A4: Option 4\n- Correct Answer: 1\n- Explanation: Explanation for the correct answer\n\n# Input Content\nRepository: {content}\nCategory: {category}\nSource: {source}\n\n# Requirements\n1. Focus on repository setup, configuration, and best practices\n2. Include questions about typical file structures, commands, or configurations\n3. Each question must have exactly 4 options with only one correct answer\n4. Provide detailed explanations for why the correct answer is appropriate\n5. Make questions practical and useful for developers working with this type of repository\n6. Consider common pitfalls and configuration issues"
3
+ }
Prompts/CreateApp.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ In this task, I want to create a website with chatbot (agent). this agent Can target a repo in GitHub. After it analyze the repo, it will give a report about the repo. The report will include the following:
2
+ - the chat agent to allow users to enter a GitHub repository URL.
3
+ - analyze that specific repository.
4
+ - answer questions about it.
5
+ - Question includes not limited to:
6
+ How to setup the project.
7
+ How to run the project.
8
+ What is the project about.
9
+ What is the project structure.
agents.py ADDED
@@ -0,0 +1,483 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import warnings
2
+ import os
3
+ import json
4
+
5
+ warnings.filterwarnings("ignore")
6
+ from together import Together
7
+
8
+ # Load configuration from config.json
9
+ def load_config():
10
+ config_path = os.path.join(os.path.dirname(__file__), "config.json")
11
+ with open(config_path, "r") as f:
12
+ return json.load(f)
13
+
14
+ # Get API key and model from config
15
+ config = load_config()
16
+ your_api_key = config["together_ai_token"]
17
+ model = config.get("model", "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8") # Use default if not in config
18
+ client = Together(api_key=your_api_key)
19
+
20
+
21
+ def prompt_llm(prompt, show_cost=False):
22
+ # This function allows us to prompt an LLM via the Together API
23
+
24
+ # Calculate the number of tokens
25
+ tokens = len(prompt.split())
26
+
27
+ # Calculate and print estimated cost for each model
28
+ if show_cost:
29
+ print(f"\nNumber of tokens: {tokens}")
30
+ cost = (0.1 / 1_000_000) * tokens
31
+ print(f"Estimated cost for {model}: ${cost:.10f}\n")
32
+
33
+ # Make the API call
34
+ response = client.chat.completions.create(
35
+ model=model,
36
+ messages=[{"role": "user", "content": prompt}],
37
+ )
38
+ return response.choices[0].message.content
39
+
40
+
41
+ class SummarizerAgent:
42
+ def __init__(self):
43
+ self.client = Together(api_key=your_api_key)
44
+
45
+ def process(self, content):
46
+ prompt = """SYSTEM: You are an expert code summarizer.
47
+ Your task is to condense the provided code into a clear, informative summary of exactly 4 lines.
48
+
49
+ INSTRUCTIONS:
50
+ β€’ Identify and include only the most important functionality
51
+ β€’ Explain what the code does and its purpose
52
+ β€’ Ensure the summary is exactly 4 lines long
53
+ β€’ Use concise, clear language
54
+ β€’ Show output only - provide just the summary
55
+ β€’ Do not include any other text or comments, show only the summary
56
+ * do not say "Here is a 4-line summary: " show the summary directly and nothing else
57
+
58
+ Code to summarize: {content}
59
+
60
+ Provide a 4-line summary:"""
61
+
62
+ return prompt_llm(prompt.format(content=content))
63
+
64
+
65
+ class InsightAgent:
66
+ def __init__(self):
67
+ self.client = Together(api_key=your_api_key)
68
+
69
+ def process_text(self, summaries):
70
+ # Process a list of summary texts directly
71
+ all_summaries = "\n\n".join(summaries)
72
+ return self._generate_insights(all_summaries)
73
+
74
+ def _generate_insights(self, all_summaries):
75
+ prompt = """SYSTEM: You are an expert code analyst who can identify key insights from code summaries.
76
+
77
+ INSTRUCTIONS:
78
+ β€’ Review the provided code summaries
79
+ β€’ Identify 3 key insights that represent the most important takeaways
80
+ β€’ Consider code structure, patterns, and best practices
81
+ β€’ Format your response as exactly 3 bullet points
82
+ β€’ Each bullet point must be a single sentence
83
+ β€’ Be concise, clear, and informative
84
+ β€’ Do not include any introductory or concluding text
85
+ β€’ Show only the 3 bullet points, nothing else
86
+
87
+ Summaries to analyze:
88
+ {summaries}
89
+
90
+ Provide exactly 3 bullet point insights:"""
91
+
92
+ return prompt_llm(prompt.format(summaries=all_summaries))
93
+
94
+
95
+ class RecommenderAgent:
96
+ def __init__(self):
97
+ self.client = Together(api_key=your_api_key)
98
+
99
+ def process(self, insights, summaries, user_goal, persona=""):
100
+ prompt = """SYSTEM: You are an expert code consultant who provides actionable recommendations.
101
+
102
+ INSTRUCTIONS:
103
+ β€’ Review the provided insights and summaries about the code
104
+ β€’ Consider the user's specific goal: {user_goal}
105
+ β€’ Consider the user's persona: {persona}
106
+ β€’ Recommend exactly 2 specific, actionable steps the user can take to improve or work with this codebase
107
+ β€’ Each recommendation should be practical, specific, and directly related to the goal
108
+ β€’ Format your response as exactly 2 bullet points
109
+ β€’ Each bullet point should be 1-2 sentences
110
+ β€’ Be concise, clear, and actionable
111
+ β€’ Do not include any introductory or concluding text
112
+ β€’ Show only the 2 bullet point recommendations, nothing else
113
+
114
+ Insights:
115
+ {insights}
116
+
117
+ Additional context from summaries:
118
+ {summaries}
119
+
120
+ User's goal: {user_goal}
121
+ User's persona: {persona}
122
+
123
+ Provide exactly 2 actionable recommendations:"""
124
+
125
+ return prompt_llm(
126
+ prompt.format(
127
+ insights=insights,
128
+ summaries="\n\n".join(summaries),
129
+ user_goal=user_goal,
130
+ persona=persona if persona else "General user",
131
+ )
132
+ )
133
+
134
+ def suggest_next_query(self, insights, summaries, user_goal, persona=""):
135
+ """Generate a suggested next search query based on insights, summaries, and user goal."""
136
+ prompt = f"""
137
+ Based on the following insights and summaries about code, and considering the user's goal and persona,
138
+ suggest ONE specific area of the codebase the user should explore next.
139
+
140
+ INSIGHTS:
141
+ {insights}
142
+
143
+ SUMMARIES:
144
+ {summaries}
145
+
146
+ USER'S GOAL:
147
+ {user_goal}
148
+
149
+ USER'S PERSONA:
150
+ {persona if persona else "General user"}
151
+
152
+ Suggest a specific, focused area or component (5-10 words) that would help the user find additional
153
+ information to achieve their goal. This should guide their next exploration of the repository.
154
+
155
+ NEXT EXPLORATION AREA:
156
+ """
157
+
158
+ return prompt_llm(prompt)
159
+
160
+
161
+ class QuestionGeneratorAgent:
162
+ def __init__(self):
163
+ self.client = Together(api_key=your_api_key)
164
+
165
+ def generate_questions(self, content, category, source):
166
+ prompt_template_path = os.path.join(os.path.dirname(__file__), "PromptTemplate.json")
167
+ with open(prompt_template_path, "r") as f:
168
+ import json
169
+ prompt_data = json.load(f)
170
+ template = prompt_data["prompt_template"]
171
+
172
+ prompt = template.format(
173
+ content=content,
174
+ category=category,
175
+ source=source
176
+ )
177
+
178
+ return prompt_llm(prompt)
179
+
180
+
181
+ class CLISetupAgent:
182
+ def __init__(self):
183
+ self.client = Together(api_key=your_api_key)
184
+
185
+ def generate_setup_instructions(self, repo_content, repo_metadata):
186
+ """Generate step-by-step CLI instructions to set up the environment for a repository."""
187
+ language = repo_metadata.get("language", "")
188
+ repo_name = repo_metadata.get("name", "")
189
+ repo_url = repo_metadata.get("url", "")
190
+
191
+ # Collect all potential setup files in the repo
192
+ setup_files = {}
193
+ common_setup_files = [
194
+ "requirements.txt", "package.json", "setup.py", "Dockerfile",
195
+ "docker-compose.yml", ".env.example", "Makefile", "README.md"
196
+ ]
197
+
198
+ for filename, content in repo_content.items():
199
+ if filename in common_setup_files or filename.endswith((".yml", ".yaml", ".sh", ".bat")):
200
+ setup_files[filename] = content
201
+
202
+ # Default setup steps if no specific files are found
203
+ default_steps = f"""
204
+ 1. Clone the repository:
205
+ ```
206
+ git clone {repo_url}
207
+ cd {repo_name}
208
+ ```
209
+
210
+ 2. Check the repository structure:
211
+ ```
212
+ ls -la
213
+ ```
214
+
215
+ 3. Read the README file for specific instructions:
216
+ ```
217
+ cat README.md
218
+ ```
219
+ """
220
+
221
+ # If we have no setup files, provide basic instructions
222
+ if not setup_files:
223
+ return default_steps
224
+
225
+ # Create a prompt with all the relevant information
226
+ prompt = f"""
227
+ SYSTEM: You are an expert DevOps engineer who provides clear CLI setup instructions.
228
+
229
+ INSTRUCTIONS:
230
+ β€’ Generate step-by-step CLI instructions to set up a development environment for the given repository
231
+ β€’ The repository is named "{repo_name}" and primarily uses {language if language else "unknown language"}
232
+ β€’ Include commands for cloning, installing dependencies, and basic configuration
233
+ β€’ Format your response as a numbered list with clear command-line instructions
234
+ β€’ Include comments explaining what each command does
235
+ β€’ Focus on practical, executable commands that work on both macOS/Linux and Windows where possible
236
+ β€’ If different platforms require different commands, clearly indicate which is for which
237
+ β€’ Mention any prerequisites that need to be installed (like Python, Node.js, Docker, etc.)
238
+
239
+ REPOSITORY INFORMATION:
240
+ Name: {repo_name}
241
+ Primary Language: {language if language else "Not specified"}
242
+ URL: {repo_url}
243
+
244
+ RELEVANT SETUP FILES:
245
+ {chr(10).join([f"--- {name} ---{chr(10)}{content[:300]}..." for name, content in setup_files.items()])}
246
+
247
+ Provide a step-by-step CLI setup guide with exactly 5-10 commands:
248
+ """
249
+
250
+ try:
251
+ result = prompt_llm(prompt)
252
+ # Check if result is empty or invalid
253
+ if not result or len(result.strip()) < 10:
254
+ return default_steps
255
+ return result
256
+ except Exception as e:
257
+ print(f"Error generating CLI setup: {str(e)}")
258
+ return default_steps
259
+
260
+
261
+ class ChatbotAgent:
262
+ """Agent for answering questions about GitHub repositories."""
263
+
264
+ def __init__(self):
265
+ self.client = Together(api_key=your_api_key)
266
+
267
+ def answer_question(self, question, repo_content, repo_metadata, summaries=None, insights=None):
268
+ """
269
+ Answer a question about a GitHub repository based on its content and analysis.
270
+
271
+ Args:
272
+ question: The user's question about the repository
273
+ repo_content: Dictionary of repository files and their content
274
+ repo_metadata: Repository metadata like name, description, etc.
275
+ summaries: Optional dictionary of file summaries
276
+ insights: Optional insights about the repository
277
+
278
+ Returns:
279
+ A string containing the answer to the question
280
+ """
281
+ # Extract key repository information
282
+ repo_name = repo_metadata.get("name", "Unknown repository")
283
+ repo_description = repo_metadata.get("description", "No description available")
284
+ repo_language = repo_metadata.get("language", "Unknown")
285
+
286
+ # Create a context from the repository information
287
+ context = f"Repository: {repo_name}\nDescription: {repo_description}\nLanguage: {repo_language}\n\n"
288
+
289
+ # Add insights if available
290
+ if insights:
291
+ context += f"Key insights:\n{insights}\n\n"
292
+
293
+ # Add summaries if available
294
+ if summaries:
295
+ context += "File summaries:\n"
296
+ for filename, summary in summaries.items():
297
+ context += f"- {filename}: {summary}\n"
298
+ context += "\n"
299
+
300
+ # Select relevant files for the question to avoid token limit issues
301
+ relevant_files = self._select_relevant_files(question, repo_content, max_files=5)
302
+
303
+ # Add content of relevant files
304
+ if relevant_files:
305
+ context += "Relevant files:\n"
306
+ for filename, content in relevant_files.items():
307
+ # Truncate long files
308
+ if len(content) > 1000:
309
+ context += f"--- {filename} (truncated) ---\n{content[:1000]}...\n\n"
310
+ else:
311
+ context += f"--- {filename} ---\n{content}\n\n"
312
+
313
+ # Create the prompt for the LLM
314
+ prompt = f"""SYSTEM: You are a GitHub repository expert assistant. You provide accurate, helpful answers
315
+ about code repositories based on their content, structure, and analysis. Draw upon the
316
+ provided context to answer the question. If you don't know the answer, say so honestly.
317
+
318
+ CONTEXT INFORMATION:
319
+ {context}
320
+
321
+ USER QUESTION:
322
+ {question}
323
+
324
+ Provide a clear, concise answer to the question based only on the information provided above.
325
+ Include code snippets or commands when relevant. Be specific and informative.
326
+ """
327
+
328
+ return prompt_llm(prompt)
329
+
330
+ def _select_relevant_files(self, question, repo_content, max_files=5):
331
+ """Select files from the repository that are most relevant to the question."""
332
+
333
+ # If there are only a few files, return all of them
334
+ if len(repo_content) <= max_files:
335
+ return repo_content
336
+
337
+ # For more files, select the most relevant ones based on the question
338
+ relevant_files = {}
339
+
340
+ # Create a prompt to identify relevant file types for the question
341
+ file_selection_prompt = f"""SYSTEM: You are a code repository expert. Given a question about a repository,
342
+ identify what types of files would be most relevant to answer it.
343
+
344
+ QUESTION: {question}
345
+
346
+ Based on this question, list ONLY 3-5 file patterns or extensions that would be most relevant
347
+ for answering it. For example: 'README.md', '.py', 'package.json', 'Dockerfile', etc.
348
+ Just list the patterns, one per line, without any explanation or additional text.
349
+ """
350
+
351
+ # Get relevant file patterns
352
+ try:
353
+ file_patterns_response = prompt_llm(file_selection_prompt)
354
+ file_patterns = [pattern.strip().lower() for pattern in file_patterns_response.split('\n') if pattern.strip()]
355
+
356
+ # Filter files based on patterns
357
+ for filename, content in repo_content.items():
358
+ filename_lower = filename.lower()
359
+
360
+ # Check if file matches any of the patterns
361
+ if any(pattern in filename_lower for pattern in file_patterns):
362
+ relevant_files[filename] = content
363
+
364
+ # Stop when we reach the maximum number of files
365
+ if len(relevant_files) >= max_files:
366
+ break
367
+
368
+ # If we didn't find enough files with patterns, add important files
369
+ if len(relevant_files) < max_files:
370
+ important_files = ['readme.md', 'setup.py', 'requirements.txt', 'package.json', 'dockerfile']
371
+
372
+ for filename, content in repo_content.items():
373
+ if filename.lower() in important_files and filename not in relevant_files:
374
+ relevant_files[filename] = content
375
+
376
+ # Stop when we reach the maximum number of files
377
+ if len(relevant_files) >= max_files:
378
+ break
379
+
380
+ # If we still don't have enough files, add some random ones
381
+ remaining_slots = max_files - len(relevant_files)
382
+ if remaining_slots > 0:
383
+ for filename, content in repo_content.items():
384
+ if filename not in relevant_files:
385
+ relevant_files[filename] = content
386
+ remaining_slots -= 1
387
+
388
+ if remaining_slots <= 0:
389
+ break
390
+
391
+ except Exception as e:
392
+ print(f"Error selecting relevant files: {str(e)}")
393
+ # Fallback: just take the first max_files
394
+ relevant_files = dict(list(repo_content.items())[:max_files])
395
+
396
+ return relevant_files
397
+
398
+
399
+ class PRReviewAgent:
400
+ """Agent for reviewing GitHub Pull Requests and providing professional code feedback."""
401
+
402
+ def __init__(self):
403
+ self.client = Together(api_key=your_api_key)
404
+
405
+ def review_pr(self, pr_details, target_branch_code):
406
+ """
407
+ Review a GitHub pull request and provide professional code suggestions.
408
+
409
+ Args:
410
+ pr_details: Dictionary containing PR files, metadata, and changes
411
+ target_branch_code: Dictionary of target branch files and their content
412
+
413
+ Returns:
414
+ A dictionary containing code suggestions and optimization recommendations
415
+ """
416
+ # Extract PR information
417
+ pr_title = pr_details.get("title", "Untitled PR")
418
+ pr_description = pr_details.get("description", "No description")
419
+ changed_files = pr_details.get("changed_files", [])
420
+
421
+ # Prepare context for the review
422
+ context = f"Pull Request: {pr_title}\nDescription: {pr_description}\n\n"
423
+
424
+ # Add changed files info
425
+ if changed_files:
426
+ context += "Files changed in this PR:\n"
427
+ for file_info in changed_files:
428
+ filename = file_info.get("filename", "unknown")
429
+ changes = file_info.get("patch", "No changes available")
430
+ context += f"--- {filename} ---\n{changes}\n\n"
431
+
432
+ # Add target branch context for the files that were changed
433
+ relevant_target_files = {}
434
+ for file_info in changed_files:
435
+ filename = file_info.get("filename", "")
436
+ if filename in target_branch_code:
437
+ relevant_target_files[filename] = target_branch_code[filename]
438
+
439
+ if relevant_target_files:
440
+ context += "Relevant files in target branch:\n"
441
+ for filename, content in relevant_target_files.items():
442
+ # Truncate long files
443
+ if len(content) > 1000:
444
+ truncated_content = content[:1000] + "..."
445
+ context += f"--- {filename} (truncated) ---\n{truncated_content}\n\n"
446
+ else:
447
+ context += f"--- {filename} ---\n{content}\n\n"
448
+
449
+ # Generate code review
450
+ code_review_prompt = f"""SYSTEM: You are a senior software developer reviewing a GitHub Pull Request.
451
+ Provide professional, constructive feedback on the code changes. Focus on:
452
+
453
+ 1. Code style and adherence to best practices
454
+ 2. Potential bugs or issues
455
+ 3. Architecture and design considerations
456
+ 4. Performance implications
457
+
458
+ CONTEXT INFORMATION:
459
+ {context}
460
+
461
+ Provide your code review in the following format:
462
+
463
+ ## Overall Assessment
464
+ [A brief 2-3 sentence assessment of the PR]
465
+
466
+ ## Code Quality Suggestions
467
+ - [Specific suggestion 1 with code example if applicable]
468
+ - [Specific suggestion 2 with code example if applicable]
469
+ - [Add more if necessary, at least 3 suggestions]
470
+
471
+ ## Optimization Opportunities
472
+ - [Specific optimization 1 with code example if applicable]
473
+ - [Specific optimization 2 with code example if applicable]
474
+ - [Add more if necessary, at least 2 suggestions]
475
+
476
+ Your review should be professional, specific, and actionable. Provide code examples where appropriate.
477
+ """
478
+
479
+ review_result = prompt_llm(code_review_prompt)
480
+
481
+ return {
482
+ "review": review_result
483
+ }
app.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, url_for, send_from_directory
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+ from agents import SummarizerAgent, InsightAgent, RecommenderAgent, QuestionGeneratorAgent, CLISetupAgent, ChatbotAgent, PRReviewAgent
5
+ from github_utils import get_repo_content, get_repo_structure, get_repo_metadata, is_github_url, get_pr_details, get_target_branch_code, verify_github_credentials
6
+ import os
7
+
8
+ app = Flask(__name__)
9
+ summarizer = SummarizerAgent()
10
+ insight_agent = InsightAgent()
11
+ recommender_agent = RecommenderAgent()
12
+ question_generator = QuestionGeneratorAgent()
13
+ cli_setup_agent = CLISetupAgent()
14
+ chatbot_agent = ChatbotAgent() # Initialize the new ChatbotAgent
15
+ pr_review_agent = PRReviewAgent() # Initialize the new PRReviewAgent
16
+
17
+ @app.route('/templates/<path:filename>')
18
+ def serve_template_file(filename):
19
+ """Serve static files from the templates directory."""
20
+ return send_from_directory(os.path.join(app.root_path, 'templates'), filename)
21
+
22
+ @app.route("/", methods=["GET", "POST"])
23
+ def index():
24
+ repo_data = None
25
+ insights = None
26
+
27
+ if request.method == "POST":
28
+ repo_url = request.form.get("repo_url", "")
29
+ if repo_url and is_github_url(repo_url):
30
+ # Get repository content
31
+ repo_data = {
32
+ "url": repo_url,
33
+ "metadata": get_repo_metadata(repo_url),
34
+ "structure": get_repo_structure(repo_url),
35
+ "content": get_repo_content(repo_url)
36
+ }
37
+
38
+ return render_template(
39
+ "index.html", repo_data=repo_data, insights=insights
40
+ )
41
+
42
+ @app.route("/summarize", methods=["POST"])
43
+ def summarize():
44
+ repo_content = request.json.get("content", {})
45
+ if not repo_content:
46
+ return jsonify({"error": "No content provided"}), 400
47
+
48
+ try:
49
+ # Generate summaries for each file
50
+ summaries = {}
51
+ for filename, content in repo_content.items():
52
+ if isinstance(content, str) and len(content) > 0:
53
+ # Limit content to 1000 words to avoid token limits
54
+ words = content.split()
55
+ if len(words) > 1000:
56
+ content_for_summary = " ".join(words[:1000])
57
+ else:
58
+ content_for_summary = content
59
+
60
+ # Generate summary
61
+ summary = summarizer.process(content_for_summary)
62
+ summaries[filename] = summary
63
+
64
+ return jsonify({"summaries": summaries})
65
+ except Exception as e:
66
+ return jsonify({"error": f"Error generating summaries: {str(e)}"}), 500
67
+
68
+ @app.route("/analyze", methods=["POST"])
69
+ def analyze():
70
+ summaries = request.json.get("summaries", {})
71
+ if not summaries:
72
+ return jsonify({"error": "No summaries provided"}), 400
73
+
74
+ try:
75
+ # Generate insights from all summaries
76
+ summary_texts = list(summaries.values())
77
+ insights = insight_agent.process_text(summary_texts)
78
+
79
+ return jsonify({"insights": insights})
80
+ except Exception as e:
81
+ return jsonify({"error": f"Error generating insights: {str(e)}"}), 500
82
+
83
+ @app.route("/recommend", methods=["POST"])
84
+ def recommend():
85
+ data = request.json
86
+ insights = data.get("insights", "")
87
+ summaries = data.get("summaries", [])
88
+ user_goal = data.get("goal", "")
89
+ persona = data.get("persona", "")
90
+
91
+ if not insights or not summaries:
92
+ return jsonify({"error": "Missing required data"}), 400
93
+
94
+ try:
95
+ recommendations = recommender_agent.process(
96
+ insights, summaries, user_goal, persona
97
+ )
98
+ next_query = recommender_agent.suggest_next_query(
99
+ insights, summaries, user_goal, persona
100
+ )
101
+ return jsonify({"recommendations": recommendations, "next_query": next_query})
102
+ except Exception as e:
103
+ return jsonify({"error": f"Error generating recommendations: {str(e)}"}), 500
104
+
105
+ @app.route("/generate_questions", methods=["POST"])
106
+ def generate_questions():
107
+ data = request.json
108
+ content = data.get("content", "")
109
+ category = data.get("category", "repository")
110
+ source = data.get("source", "")
111
+
112
+ if not content or not source:
113
+ return jsonify({"error": "Missing required data"}), 400
114
+
115
+ try:
116
+ questions = question_generator.generate_questions(content, category, source)
117
+ return jsonify({"questions": questions})
118
+ except Exception as e:
119
+ return jsonify({"error": f"Error generating questions: {str(e)}"}), 500
120
+
121
+ @app.route("/workflow", methods=["POST"])
122
+ def workflow():
123
+ """Complete workflow from repository URL to recommendations."""
124
+ repo_url = request.json.get("repo_url", "")
125
+ user_goal = request.json.get("goal", "Understand the codebase")
126
+ persona = request.json.get("persona", "Developer")
127
+ github_auth = request.json.get("github_auth", None)
128
+
129
+ if not repo_url or not is_github_url(repo_url):
130
+ return jsonify({"error": "Valid GitHub repository URL required"}), 400
131
+
132
+ try:
133
+ # Prepare authentication if provided
134
+ auth = None
135
+ if github_auth and 'username' in github_auth and 'token' in github_auth:
136
+ auth = (github_auth['username'], github_auth['token'])
137
+
138
+ # Step 1: Get repository content
139
+ repo_content = get_repo_content(repo_url, auth=auth)
140
+ if "error" in repo_content:
141
+ return jsonify({"error": repo_content["error"]}), 500
142
+
143
+ # Get repository metadata
144
+ repo_metadata = get_repo_metadata(repo_url, auth=auth)
145
+ # Ensure repo_metadata has the URL
146
+ if "url" not in repo_metadata:
147
+ repo_metadata["url"] = repo_url
148
+
149
+ # Step 2: Generate summaries
150
+ summaries = {}
151
+ for filename, content in repo_content.items():
152
+ words = content.split()
153
+ if len(words) > 1000:
154
+ content_for_summary = " ".join(words[:1000])
155
+ else:
156
+ content_for_summary = content
157
+
158
+ summary = summarizer.process(content_for_summary)
159
+ summaries[filename] = summary
160
+
161
+ # New Step: Generate CLI setup instructions
162
+ try:
163
+ cli_setup = cli_setup_agent.generate_setup_instructions(repo_content, repo_metadata)
164
+ if not cli_setup or len(cli_setup.strip()) < 10:
165
+ cli_setup = "Sorry, couldn't generate setup instructions for this repository."
166
+ except Exception as e:
167
+ print(f"Error in CLI setup generation: {str(e)}")
168
+ cli_setup = "Error generating setup instructions. Please check the repository and try again."
169
+
170
+ # Step 3: Generate insights
171
+ summary_texts = list(summaries.values())
172
+ insights = insight_agent.process_text(summary_texts)
173
+
174
+ # Step 4: Generate recommendations
175
+ recommendations = recommender_agent.process(
176
+ insights, summary_texts, user_goal, persona
177
+ )
178
+
179
+ # Step 5: Suggest next exploration area
180
+ next_area = recommender_agent.suggest_next_query(
181
+ insights, summary_texts, user_goal, persona
182
+ )
183
+
184
+ # Step 6: Generate questions
185
+ repo_name = repo_metadata.get("name", "GitHub Repository")
186
+ questions = question_generator.generate_questions(
187
+ repo_name, "repository", repo_url
188
+ )
189
+
190
+ return jsonify({
191
+ "summaries": summaries,
192
+ "cli_setup": cli_setup,
193
+ "insights": insights,
194
+ "recommendations": recommendations,
195
+ "next_area": next_area,
196
+ "questions": questions,
197
+ "repo_content": repo_content, # Add repository content for the chatbot
198
+ "repo_metadata": repo_metadata # Add repository metadata for the chatbot
199
+ })
200
+
201
+ except Exception as e:
202
+ return jsonify({"error": f"Error in workflow: {str(e)}"}), 500
203
+
204
+ @app.route("/chat", methods=["POST"])
205
+ def chat():
206
+ """Handle chatbot questions about a repository."""
207
+ data = request.json
208
+ question = data.get("question", "")
209
+ repo_url = data.get("repo_url", "")
210
+ repo_content = data.get("repo_content", {})
211
+ repo_metadata = data.get("repo_metadata", {})
212
+ summaries = data.get("summaries", {})
213
+ insights = data.get("insights", "")
214
+ github_auth = data.get("github_auth", None)
215
+
216
+ if not question:
217
+ return jsonify({"error": "No question provided"}), 400
218
+
219
+ # Prepare authentication if provided
220
+ auth = None
221
+ if github_auth and 'username' in github_auth and 'token' in github_auth:
222
+ auth = (github_auth['username'], github_auth['token'])
223
+
224
+ if not repo_content and repo_url:
225
+ # If content isn't provided but URL is, fetch the repository content
226
+ if is_github_url(repo_url):
227
+ repo_content = get_repo_content(repo_url, auth=auth)
228
+ repo_metadata = get_repo_metadata(repo_url, auth=auth)
229
+ # Ensure repo_metadata has the URL
230
+ if "url" not in repo_metadata:
231
+ repo_metadata["url"] = repo_url
232
+ else:
233
+ return jsonify({"error": "Valid GitHub repository URL required"}), 400
234
+
235
+ if not repo_content:
236
+ return jsonify({"error": "No repository content provided"}), 400
237
+
238
+ try:
239
+ # Use the chatbot agent to answer the question
240
+ answer = chatbot_agent.answer_question(
241
+ question=question,
242
+ repo_content=repo_content,
243
+ repo_metadata=repo_metadata,
244
+ summaries=summaries,
245
+ insights=insights
246
+ )
247
+
248
+ return jsonify({
249
+ "answer": answer,
250
+ "question": question
251
+ })
252
+ except Exception as e:
253
+ return jsonify({"error": f"Error answering question: {str(e)}"}), 500
254
+
255
+ @app.route("/review_pr", methods=["POST"])
256
+ def review_pr():
257
+ """Review a GitHub Pull Request and provide professional code suggestions."""
258
+ pr_url = request.json.get("pr_url", "")
259
+ max_files = request.json.get("max_files", 25) # Default to 25 files
260
+ file_types = request.json.get("file_types", None) # Default to all code files
261
+ github_auth = request.json.get("github_auth", None) # GitHub authentication
262
+
263
+ if not pr_url:
264
+ return jsonify({"error": "No PR URL provided"}), 400
265
+
266
+ try:
267
+ # Prepare authentication if provided
268
+ auth = None
269
+ if github_auth and 'username' in github_auth and 'token' in github_auth:
270
+ auth = (github_auth['username'], github_auth['token'])
271
+
272
+ # Step 1: Fetch PR details
273
+ pr_details = get_pr_details(pr_url, max_files=max_files, file_types=file_types, auth=auth)
274
+ if "error" in pr_details:
275
+ return jsonify({"error": pr_details["error"]}), 500
276
+
277
+ # Step 2: Fetch target branch code
278
+ target_branch_code = get_target_branch_code(pr_url, max_files=max_files, file_types=file_types, auth=auth)
279
+ if "error" in target_branch_code:
280
+ return jsonify({"error": target_branch_code["error"]}), 500
281
+
282
+ # Step 3: Generate PR review
283
+ review_result = pr_review_agent.review_pr(pr_details, target_branch_code)
284
+
285
+ # Step 4: Return the results
286
+ return jsonify({
287
+ "pr_title": pr_details.get("title", ""),
288
+ "pr_user": pr_details.get("user", ""),
289
+ "target_branch": pr_details.get("target_branch", ""),
290
+ "source_branch": pr_details.get("source_branch", ""),
291
+ "changed_files_count": len(pr_details.get("changed_files", [])),
292
+ "total_file_count": pr_details.get("total_file_count", 0),
293
+ "review": review_result.get("review", "Error generating review"),
294
+ "analyzed_files": [file["filename"] for file in pr_details.get("changed_files", [])]
295
+ })
296
+
297
+ except Exception as e:
298
+ return jsonify({"error": f"Error reviewing PR: {str(e)}"}), 500
299
+
300
+ @app.route("/verify_github_credentials", methods=["POST"])
301
+ def verify_credentials():
302
+ """Verify GitHub credentials and return status."""
303
+ data = request.json
304
+ github_username = data.get("github_username", "")
305
+ github_token = data.get("github_token", "")
306
+
307
+ if not github_username or not github_token:
308
+ return jsonify({"valid": False, "error": "Missing username or token"}), 400
309
+
310
+ # Verify the credentials
311
+ is_valid = verify_github_credentials(github_username, github_token)
312
+
313
+ if is_valid:
314
+ return jsonify({"valid": True, "message": "Successfully authenticated with GitHub"})
315
+ else:
316
+ return jsonify({"valid": False, "error": "Invalid GitHub credentials"}), 401
317
+
318
+ if __name__ == "__main__":
319
+ app.run(debug=True, port=5001, host="0.0.0.0")
github_utils.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from bs4 import BeautifulSoup
3
+ import base64
4
+ import re
5
+ import os
6
+ from urllib.parse import urlparse
7
+
8
+ def is_github_url(url):
9
+ """Check if a URL is a GitHub repository URL."""
10
+ parsed = urlparse(url)
11
+ return parsed.netloc in ['github.com', 'www.github.com']
12
+
13
+ def parse_github_url(url):
14
+ """Extract owner and repo from GitHub URL."""
15
+ parts = url.strip('/').split('/')
16
+ if 'github.com' in parts:
17
+ idx = parts.index('github.com')
18
+ if len(parts) > idx + 2:
19
+ owner = parts[idx + 1]
20
+ repo = parts[idx + 2]
21
+ return owner, repo
22
+ return None, None
23
+
24
+ def get_repo_content(url, auth=None):
25
+ """
26
+ Get content from a GitHub repository using GitHub's API.
27
+ Returns a dictionary of filenames and their content.
28
+
29
+ Args:
30
+ url: GitHub repository URL
31
+ auth: Optional tuple of (username, token) for authentication
32
+ """
33
+ owner, repo = parse_github_url(url)
34
+ if not owner or not repo:
35
+ return {"error": "Invalid GitHub URL format"}
36
+
37
+ try:
38
+ # Fetch repository contents
39
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/contents"
40
+ headers = {}
41
+
42
+ # Add authentication if provided
43
+ if auth and len(auth) == 2:
44
+ username, token = auth
45
+ auth_header = base64.b64encode(f"{username}:{token}".encode()).decode()
46
+ headers["Authorization"] = f"Basic {auth_header}"
47
+
48
+ response = requests.get(api_url, headers=headers)
49
+ response.raise_for_status()
50
+
51
+ contents = response.json()
52
+ repo_content = {}
53
+
54
+ # Process each file/directory
55
+ for item in contents:
56
+ if item['type'] == 'file' and item['name'].endswith(('.py', '.js', '.html', '.css', '.md')):
57
+ # Get file content
58
+ file_response = requests.get(item['url'], headers=headers)
59
+ file_response.raise_for_status()
60
+ file_data = file_response.json()
61
+
62
+ if 'content' in file_data:
63
+ content = base64.b64decode(file_data['content']).decode('utf-8')
64
+ repo_content[item['name']] = content
65
+
66
+ # Limit to first 5 files to avoid exceeding API limits
67
+ if len(repo_content) >= 5:
68
+ break
69
+
70
+ return repo_content
71
+
72
+ except Exception as e:
73
+ return {"error": f"Error fetching repository: {str(e)}"}
74
+
75
+ def get_repo_structure(url, auth=None):
76
+ """
77
+ Get the structure of a GitHub repository.
78
+ Returns a list of file paths in the repository.
79
+
80
+ Args:
81
+ url: GitHub repository URL
82
+ auth: Optional tuple of (username, token) for authentication
83
+ """
84
+ owner, repo = parse_github_url(url)
85
+ if not owner or not repo:
86
+ return {"error": "Invalid GitHub URL format"}
87
+
88
+ try:
89
+ # Prepare headers for authentication
90
+ headers = {}
91
+ if auth and len(auth) == 2:
92
+ username, token = auth
93
+ auth_header = base64.b64encode(f"{username}:{token}".encode()).decode()
94
+ headers["Authorization"] = f"Basic {auth_header}"
95
+
96
+ # Use GitHub's API to get repository contents
97
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/main?recursive=1"
98
+ response = requests.get(api_url, headers=headers)
99
+
100
+ # If 'main' branch doesn't exist, try 'master'
101
+ if response.status_code != 200:
102
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/master?recursive=1"
103
+ response = requests.get(api_url, headers=headers)
104
+
105
+ response.raise_for_status()
106
+ data = response.json()
107
+
108
+ # Extract file paths
109
+ files = [item['path'] for item in data['tree'] if item['type'] == 'blob']
110
+ return files
111
+
112
+ except Exception as e:
113
+ return {"error": f"Error fetching repository structure: {str(e)}"}
114
+
115
+ def get_repo_metadata(url, auth=None):
116
+ """
117
+ Get metadata about a GitHub repository such as description, stars, etc.
118
+
119
+ Args:
120
+ url: GitHub repository URL
121
+ auth: Optional tuple of (username, token) for authentication
122
+ """
123
+ owner, repo = parse_github_url(url)
124
+ if not owner or not repo:
125
+ return {"error": "Invalid GitHub URL format"}
126
+
127
+ try:
128
+ # Use GitHub's API to get repository information
129
+ api_url = f"https://api.github.com/repos/{owner}/{repo}"
130
+
131
+ # Prepare headers for authentication
132
+ headers = {}
133
+ if auth and len(auth) == 2:
134
+ username, token = auth
135
+ auth_header = base64.b64encode(f"{username}:{token}".encode()).decode()
136
+ headers["Authorization"] = f"Basic {auth_header}"
137
+
138
+ response = requests.get(api_url, headers=headers)
139
+ response.raise_for_status()
140
+
141
+ data = response.json()
142
+ return {
143
+ "name": data.get("name", ""),
144
+ "description": data.get("description", ""),
145
+ "stars": data.get("stargazers_count", 0),
146
+ "forks": data.get("forks_count", 0),
147
+ "language": data.get("language", ""),
148
+ "url": data.get("html_url", "")
149
+ }
150
+
151
+ except Exception as e:
152
+ return {"error": f"Error fetching repository metadata: {str(e)}"}
153
+
154
+ def parse_github_pr_url(url):
155
+ """Extract owner, repo, and PR number from GitHub PR URL."""
156
+ pattern = r'https?://github\.com/([^/]+)/([^/]+)/pull/(\d+)'
157
+ match = re.match(pattern, url)
158
+ if match:
159
+ owner, repo, pr_number = match.groups()
160
+ return owner, repo, pr_number
161
+ return None, None, None
162
+
163
+ def get_pr_details(pr_url, max_files=25, file_types=None, auth=None):
164
+ """
165
+ Get details of a GitHub Pull Request including changed files and their contents.
166
+ Returns a dictionary with PR metadata and changes.
167
+
168
+ Args:
169
+ pr_url: URL of the GitHub PR
170
+ max_files: Maximum number of files to fetch (default: 25)
171
+ file_types: List of file extensions to include (default: None = all code files)
172
+ auth: Optional tuple of (username, token) for authentication
173
+ """
174
+ owner, repo, pr_number = parse_github_pr_url(pr_url)
175
+ if not owner or not repo or not pr_number:
176
+ return {"error": "Invalid GitHub PR URL format"}
177
+
178
+ try:
179
+ # Prepare headers for authentication
180
+ headers = {}
181
+ if auth and len(auth) == 2:
182
+ username, token = auth
183
+ auth_header = base64.b64encode(f"{username}:{token}".encode()).decode()
184
+ headers["Authorization"] = f"Basic {auth_header}"
185
+
186
+ # Fetch PR information
187
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
188
+ response = requests.get(api_url, headers=headers)
189
+ response.raise_for_status()
190
+
191
+ pr_data = response.json()
192
+
193
+ # Get PR metadata
194
+ pr_details = {
195
+ "title": pr_data.get("title", ""),
196
+ "description": pr_data.get("body", ""),
197
+ "user": pr_data.get("user", {}).get("login", ""),
198
+ "state": pr_data.get("state", ""),
199
+ "created_at": pr_data.get("created_at", ""),
200
+ "updated_at": pr_data.get("updated_at", ""),
201
+ "target_branch": pr_data.get("base", {}).get("ref", ""),
202
+ "source_branch": pr_data.get("head", {}).get("ref", ""),
203
+ "changed_files": [],
204
+ "total_file_count": pr_data.get("changed_files", 0)
205
+ }
206
+
207
+ # Default file types to include if not specified
208
+ if file_types is None:
209
+ file_types = ['.py', '.js', '.html', '.css', '.md', '.java', '.ts', '.jsx',
210
+ '.tsx', '.go', '.c', '.cpp', '.h', '.hpp', '.json', '.yml',
211
+ '.yaml', '.sh', '.txt', '.sql']
212
+
213
+ # Fetch PR changed files with pagination
214
+ page = 1
215
+
216
+ while True:
217
+ files_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/files?per_page=100&page={page}"
218
+ files_response = requests.get(files_url, headers=headers)
219
+ files_response.raise_for_status()
220
+
221
+ files_data = files_response.json()
222
+
223
+ # If no more files, break the loop
224
+ if not files_data:
225
+ break
226
+
227
+ # Process each file in this page
228
+ for file_data in files_data:
229
+ filename = file_data.get("filename", "")
230
+
231
+ # Skip binary files and non-code files
232
+ file_ext = os.path.splitext(filename)[1].lower()
233
+ if file_types and file_ext not in file_types:
234
+ continue
235
+
236
+ file_info = {
237
+ "filename": filename,
238
+ "status": file_data.get("status", ""), # added, modified, removed
239
+ "additions": file_data.get("additions", 0),
240
+ "deletions": file_data.get("deletions", 0),
241
+ "patch": file_data.get("patch", "")
242
+ }
243
+
244
+ # Add file content if it exists in the PR
245
+ if file_data.get("status") != "removed":
246
+ try:
247
+ file_content_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{pr_data['head']['sha']}/{filename}"
248
+ content_response = requests.get(file_content_url, headers=headers)
249
+
250
+ if content_response.status_code == 200:
251
+ file_info["content"] = content_response.text
252
+ except Exception as e:
253
+ file_info["content_error"] = str(e)
254
+
255
+ pr_details["changed_files"].append(file_info)
256
+
257
+ # Stop when we reach the maximum number of files
258
+ if len(pr_details["changed_files"]) >= max_files:
259
+ break
260
+
261
+ # If we've reached max files or there are no more pages, break
262
+ if len(pr_details["changed_files"]) >= max_files or len(files_data) < 100:
263
+ break
264
+
265
+ # Move to next page
266
+ page += 1
267
+
268
+ return pr_details
269
+
270
+ except Exception as e:
271
+ return {"error": f"Error fetching PR details: {str(e)}"}
272
+
273
+ def get_target_branch_code(pr_url, max_files=25, file_types=None, auth=None):
274
+ """
275
+ Get the code from the target branch of a PR.
276
+ Returns a dictionary of filenames and their content from the target branch.
277
+
278
+ Args:
279
+ pr_url: URL of the GitHub PR
280
+ max_files: Maximum number of files to fetch (default: 25)
281
+ file_types: List of file extensions to include (default: None = all code files)
282
+ auth: Optional tuple of (username, token) for authentication
283
+ """
284
+ owner, repo, pr_number = parse_github_pr_url(pr_url)
285
+ if not owner or not repo or not pr_number:
286
+ return {"error": "Invalid GitHub PR URL format"}
287
+
288
+ try:
289
+ # Prepare headers for authentication
290
+ headers = {}
291
+ if auth and len(auth) == 2:
292
+ username, token = auth
293
+ auth_header = base64.b64encode(f"{username}:{token}".encode()).decode()
294
+ headers["Authorization"] = f"Basic {auth_header}"
295
+
296
+ # First get the PR to find the target branch name
297
+ api_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
298
+ response = requests.get(api_url, headers=headers)
299
+ response.raise_for_status()
300
+
301
+ pr_data = response.json()
302
+ target_branch = pr_data.get("base", {}).get("ref", "main") # Default to main if not found
303
+
304
+ # Default file types to include if not specified
305
+ if file_types is None:
306
+ file_types = ['.py', '.js', '.html', '.css', '.md', '.java', '.ts', '.jsx',
307
+ '.tsx', '.go', '.c', '.cpp', '.h', '.hpp', '.json', '.yml',
308
+ '.yaml', '.sh', '.txt', '.sql']
309
+
310
+ # Get files that were changed in the PR with pagination
311
+ page = 1
312
+ target_branch_code = {}
313
+
314
+ while True:
315
+ files_url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}/files?per_page=100&page={page}"
316
+ files_response = requests.get(files_url, headers=headers)
317
+ files_response.raise_for_status()
318
+
319
+ files_data = files_response.json()
320
+
321
+ # If no more files, break the loop
322
+ if not files_data:
323
+ break
324
+
325
+ # Get the changed filenames from this page
326
+ for file_data in files_data:
327
+ filename = file_data.get("filename")
328
+
329
+ # Skip if filename is None or non-matching extension
330
+ if not filename:
331
+ continue
332
+
333
+ file_ext = os.path.splitext(filename)[1].lower()
334
+ if file_types and file_ext not in file_types:
335
+ continue
336
+
337
+ try:
338
+ # Get file content from target branch
339
+ file_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{target_branch}/{filename}"
340
+ file_response = requests.get(file_url, headers=headers)
341
+
342
+ if file_response.status_code == 200:
343
+ target_branch_code[filename] = file_response.text
344
+ except Exception as e:
345
+ print(f"Error fetching {filename} from target branch: {str(e)}")
346
+
347
+ # Stop when we reach the maximum number of files
348
+ if len(target_branch_code) >= max_files:
349
+ break
350
+
351
+ # If we've reached max files or there are no more pages, break
352
+ if len(target_branch_code) >= max_files or len(files_data) < 100:
353
+ break
354
+
355
+ # Move to next page
356
+ page += 1
357
+
358
+ return target_branch_code
359
+
360
+ except Exception as e:
361
+ return {"error": f"Error fetching target branch code: {str(e)}"}
362
+
363
+ def verify_github_credentials(username, token):
364
+ """
365
+ Verify GitHub credentials by making a test API call.
366
+ Returns True if credentials are valid, False otherwise.
367
+
368
+ Args:
369
+ username: GitHub username
370
+ token: GitHub personal access token
371
+ """
372
+ try:
373
+ # Create authentication header
374
+ auth_header = base64.b64encode(f"{username}:{token}".encode()).decode()
375
+ headers = {"Authorization": f"Basic {auth_header}"}
376
+
377
+ # Make a test API call to get user information
378
+ response = requests.get("https://api.github.com/user", headers=headers)
379
+
380
+ # Return True if the request was successful (status code 200)
381
+ return response.status_code == 200
382
+ except Exception as e:
383
+ print(f"Error verifying GitHub credentials: {str(e)}")
384
+ return False
message.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Create a flask app with templates/index.html using DuckDuckGo deep research agent. When the user search for something on the search bar. Try to search the GitHub Repo and return max 5 results use duckduckgo_search for this.
2
+
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ flask==2.3.3
2
+ requests==2.31.0
3
+ duckduckgo-search==3.0.2
templates/cat_style.css ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --cat-primary: #FF9D87;
3
+ --cat-secondary: #FFCBA4;
4
+ --cat-accent: #8C5E58;
5
+ --cat-dark: #594545;
6
+ --cat-light: #FFF5E4;
7
+ }
8
+
9
+ body {
10
+ padding-top: 2rem;
11
+ padding-bottom: 2rem;
12
+ background-color: var(--cat-light);
13
+ font-family: 'Comic Sans MS', cursive, sans-serif;
14
+ background-image: url('https://www.transparenttextures.com/patterns/paws.png');
15
+ }
16
+
17
+ .loading {
18
+ display: none;
19
+ text-align: center;
20
+ margin: 20px 0;
21
+ }
22
+
23
+ .card {
24
+ margin-bottom: 20px;
25
+ border-radius: 15px;
26
+ border: 2px solid var(--cat-accent);
27
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
28
+ background-color: white;
29
+ }
30
+
31
+ .card-body {
32
+ position: relative;
33
+ }
34
+
35
+ .card-title {
36
+ color: var(--cat-dark);
37
+ font-weight: bold;
38
+ display: inline-block;
39
+ }
40
+
41
+ .card-title::before {
42
+ content: "🐱 ";
43
+ }
44
+
45
+ .btn-primary {
46
+ background-color: var(--cat-primary);
47
+ border-color: var(--cat-accent);
48
+ color: var(--cat-dark);
49
+ font-weight: bold;
50
+ transition: all 0.3s;
51
+ }
52
+
53
+ .btn-primary:hover {
54
+ background-color: var(--cat-accent);
55
+ border-color: var(--cat-dark);
56
+ transform: scale(1.05);
57
+ }
58
+
59
+ h1 {
60
+ color: var(--cat-accent);
61
+ text-align: center;
62
+ font-weight: bold;
63
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
64
+ }
65
+
66
+ h1::before, h1::after {
67
+ content: " 🐾 ";
68
+ }
69
+
70
+ .form-control, .form-select {
71
+ border: 2px solid var(--cat-secondary);
72
+ border-radius: 10px;
73
+ background-color: var(--cat-light);
74
+ }
75
+
76
+ .form-control:focus, .form-select:focus {
77
+ border-color: var(--cat-primary);
78
+ box-shadow: 0 0 0 0.25rem rgba(255, 157, 135, 0.25);
79
+ }
80
+
81
+ .form-label {
82
+ color: var(--cat-dark);
83
+ font-weight: bold;
84
+ }
85
+
86
+ .cat-corner {
87
+ position: absolute;
88
+ width: 50px;
89
+ height: 50px;
90
+ opacity: 0.7;
91
+ }
92
+
93
+ .cat-top-right {
94
+ top: -25px;
95
+ right: -15px;
96
+ transform: rotate(45deg);
97
+ }
98
+
99
+ .spinner-border {
100
+ color: var(--cat-primary) !important;
101
+ }
102
+
103
+ .paw-list {
104
+ list-style-type: none;
105
+ padding-left: 10px;
106
+ }
107
+
108
+ .paw-list li::before {
109
+ content: "🐾";
110
+ margin-right: 10px;
111
+ }
112
+
113
+ #loading p {
114
+ color: var(--cat-accent);
115
+ font-weight: bold;
116
+ margin-top: 15px;
117
+ }
118
+
119
+ #loading img {
120
+ width: 100px;
121
+ height: auto;
122
+ }
123
+
124
+ .cat-cursor {
125
+ cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>🐱</text></svg>") 16 0, auto;
126
+ }
127
+
128
+ /* Special styles for results section */
129
+ #results pre {
130
+ background-color: var(--cat-light);
131
+ padding: 15px;
132
+ border-radius: 10px;
133
+ border: 1px dashed var(--cat-primary);
134
+ }
135
+
136
+ /* CLI setup instructions styling */
137
+ .cli-setup pre.cli-instructions {
138
+ background-color: var(--cat-light);
139
+ color: var(--cat-dark);
140
+ padding: 15px;
141
+ border-radius: 10px;
142
+ font-family: 'Courier New', monospace;
143
+ white-space: pre-wrap;
144
+ border-left: 5px solid var(--cat-primary);
145
+ overflow-x: auto;
146
+ }
147
+
148
+ .cli-setup pre.cli-instructions::before {
149
+ content: "🐈 $ ";
150
+ color: var(--cat-primary);
151
+ }
152
+
153
+ /* Footer styles */
154
+ footer {
155
+ color: var(--cat-accent);
156
+ margin-top: 2rem;
157
+ }
158
+
159
+ footer .fa-heart {
160
+ color: var(--cat-primary);
161
+ }
162
+
163
+ footer .fa-cat, footer .fa-paw {
164
+ font-size: 20px;
165
+ margin-right: 0.5rem;
166
+ }
167
+
168
+ /* Chat interface styling */
169
+ .chat-messages {
170
+ max-height: calc(80vh - 260px); /* Adjusted to leave room for input box */
171
+ overflow-y: auto;
172
+ padding: 10px;
173
+ border: 1px solid var(--cat-secondary);
174
+ border-radius: 10px;
175
+ background-color: var(--cat-light);
176
+ flex-grow: 1;
177
+ }
178
+
179
+ #chat-container {
180
+ display: flex;
181
+ flex-direction: column;
182
+ min-height: 250px;
183
+ height: calc(80vh - 200px); /* Take up 80% of viewport height minus space for headers */
184
+ max-height: 80vh; /* Maximum height is 80% of viewport height */
185
+ transition: all 0.3s ease;
186
+ }
187
+
188
+ .chat-message {
189
+ margin-bottom: 15px;
190
+ padding: 10px;
191
+ border-radius: 10px;
192
+ max-width: 80%;
193
+ }
194
+
195
+ .user-message {
196
+ background-color: var(--cat-primary);
197
+ color: white;
198
+ margin-left: auto;
199
+ border-bottom-right-radius: 2px;
200
+ }
201
+
202
+ .bot-message {
203
+ background-color: var(--cat-secondary);
204
+ color: var(--cat-dark);
205
+ margin-right: auto;
206
+ border-bottom-left-radius: 2px;
207
+ }
208
+
209
+ .chat-loading {
210
+ color: var(--cat-accent);
211
+ font-style: italic;
212
+ }
213
+
214
+ .chat-input-container {
215
+ margin-top: 10px;
216
+ }
217
+
218
+ .message-time {
219
+ font-size: 0.7rem;
220
+ color: #666;
221
+ display: block;
222
+ margin-top: 5px;
223
+ }
224
+
225
+ .chat-message::before {
226
+ content: "";
227
+ display: block;
228
+ font-size: 0.8rem;
229
+ margin-bottom: 5px;
230
+ }
231
+
232
+ .user-message::before {
233
+ content: "You";
234
+ color: white;
235
+ }
236
+
237
+ .bot-message::before {
238
+ content: "Repo Cat 🐱";
239
+ color: var(--cat-dark);
240
+ }
241
+
242
+ /* Empty chat messages placeholder */
243
+ .chat-messages:empty::before {
244
+ content: "No messages yet. Ask Repo Cat a question about this repository!";
245
+ color: #999;
246
+ font-style: italic;
247
+ display: block;
248
+ text-align: center;
249
+ padding: 20px 0;
250
+ }
templates/index.html ADDED
@@ -0,0 +1,1158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Purr-fect Code Analyzer</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
9
+ <link rel="stylesheet" href="{{ url_for('serve_template_file', filename='cat_style.css') }}">
10
+ </head>
11
+ <body class="cat-cursor">
12
+ <div class="container">
13
+ <h1 class="mb-4">Purr-fect Code Analyzer</h1>
14
+
15
+ <!-- Navigation tabs -->
16
+ <ul class="nav nav-tabs mb-4" id="mainTabs" role="tablist">
17
+ <li class="nav-item" role="presentation">
18
+ <button class="nav-link active" id="agent-tab" data-bs-toggle="tab" data-bs-target="#agent-panel" type="button" role="tab" aria-controls="agent-panel" aria-selected="true">
19
+ <i class="fas fa-cat me-2"></i>Repository Agent
20
+ </button>
21
+ </li>
22
+ <li class="nav-item" role="presentation">
23
+ <button class="nav-link" id="repo-tab" data-bs-toggle="tab" data-bs-target="#repo-panel" type="button" role="tab" aria-controls="repo-panel" aria-selected="false">
24
+ <i class="fas fa-code-branch me-2"></i>Repository Analysis
25
+ </button>
26
+ </li>
27
+ <li class="nav-item" role="presentation">
28
+ <button class="nav-link" id="pr-tab" data-bs-toggle="tab" data-bs-target="#pr-panel" type="button" role="tab" aria-controls="pr-panel" aria-selected="false">
29
+ <i class="fas fa-code-pull-request me-2"></i>PR Review
30
+ </button>
31
+ </li>
32
+ <li class="nav-item ms-auto" role="presentation">
33
+ <button class="nav-link" id="login-tab" data-bs-toggle="tab" data-bs-target="#login-panel" type="button" role="tab" aria-controls="login-panel" aria-selected="false">
34
+ <i class="fas fa-sign-in-alt me-2"></i>GitHub Login
35
+ </button>
36
+ </li>
37
+ </ul>
38
+
39
+ <!-- Tab content -->
40
+ <div class="tab-content" id="mainTabsContent">
41
+ <!-- Repository Agent Tab (New default tab) -->
42
+ <div class="tab-pane fade show active" id="agent-panel" role="tabpanel" aria-labelledby="agent-tab">
43
+ <div class="card mb-4">
44
+ <div class="card-body">
45
+ <h5 class="card-title">Connect to GitHub Repository</h5>
46
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864514.png" class="cat-corner cat-top-right">
47
+ <form id="agentRepoForm">
48
+ <div class="mb-3">
49
+ <label for="agent_repo_url" class="form-label">GitHub Repository URL <i class="fas fa-cat" style="color: var(--cat-accent);"></i></label>
50
+ <input type="url" class="form-control" id="agent_repo_url" name="agent_repo_url"
51
+ placeholder="https://github.com/username/repository" required>
52
+ </div>
53
+ <button type="submit" class="btn btn-primary">
54
+ <i class="fas fa-plug me-2"></i> Connect to Repository
55
+ </button>
56
+ </form>
57
+ </div>
58
+ </div>
59
+
60
+ <div id="agent-loading" class="loading">
61
+ <img src="https://media.giphy.com/media/VbnUQpnihPSIgIXuZv/giphy.gif" alt="Cat loading animation">
62
+ <p>Connecting to repository... Our cat agent is preparing for communication!</p>
63
+ </div>
64
+
65
+ <div id="agent-interface" class="d-none">
66
+ <div class="card mb-3">
67
+ <div class="card-body">
68
+ <h5 class="card-title">Chat with Repo Cat</h5>
69
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864654.png" class="cat-corner cat-top-right">
70
+ <p class="card-text">Ask any question about this repository and get immediate answers!</p>
71
+
72
+ <div id="chat-container" class="d-flex flex-column">
73
+ <div id="chat-messages" class="chat-messages mb-3 flex-grow-1"></div>
74
+
75
+ <div class="chat-input-container d-flex mt-auto">
76
+ <input type="text" id="chat-input" class="form-control me-2"
77
+ placeholder="Ask about setup, functionality, or code structure...">
78
+ <button id="chat-submit" class="btn btn-primary">
79
+ <i class="fas fa-paper-plane"></i>
80
+ </button>
81
+ </div>
82
+ <div id="chat-loading" class="chat-loading mt-2 d-none">
83
+ <div class="spinner-border spinner-border-sm text-primary" role="status">
84
+ <span class="visually-hidden">Loading...</span>
85
+ </div>
86
+ <span class="ms-2">Repo Cat is thinking...</span>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <!-- Repository Analysis Tab (No longer default) -->
95
+ <div class="tab-pane fade" id="repo-panel" role="tabpanel" aria-labelledby="repo-tab">
96
+ <div class="card mb-4">
97
+ <div class="card-body">
98
+ <h5 class="card-title">Analyze GitHub Repository</h5>
99
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864514.png" class="cat-corner cat-top-right">
100
+ <form id="repoForm">
101
+ <div class="mb-3">
102
+ <label for="repo_url" class="form-label">GitHub Repository URL <i class="fas fa-cat" style="color: var(--cat-accent);"></i></label>
103
+ <input type="url" class="form-control" id="repo_url" name="repo_url"
104
+ placeholder="https://github.com/username/repository" required>
105
+ </div>
106
+ <div class="mb-3">
107
+ <label for="goal" class="form-label">Your Goal <i class="fas fa-fish" style="color: var(--cat-primary);"></i></label>
108
+ <input type="text" class="form-control" id="goal" name="goal"
109
+ placeholder="Understand the codebase" value="Understand the codebase">
110
+ </div>
111
+ <div class="mb-3">
112
+ <label for="persona" class="form-label">Your Purr-sona <i class="fas fa-paw" style="color: var(--cat-accent);"></i></label>
113
+ <select class="form-select" id="persona" name="persona">
114
+ <option value="Developer">Developer Cat</option>
115
+ <option value="Project Manager">Project Manager Cat</option>
116
+ <option value="Student">Student Kitten</option>
117
+ <option value="Researcher">Researcher Cat</option>
118
+ </select>
119
+ </div>
120
+ <button type="submit" class="btn btn-primary">
121
+ <i class="fas fa-cat me-2"></i> Analyze Re-paw-sitory
122
+ </button>
123
+ </form>
124
+ </div>
125
+ </div>
126
+
127
+ <div id="loading" class="loading">
128
+ <img src="https://media.giphy.com/media/LmNwrBhejkK9EFP504/giphy.gif" alt="Cat typing animation">
129
+ <p>Analyzing repository... Our cat experts are pawing through the code!</p>
130
+ </div>
131
+
132
+ <div id="results" class="d-none">
133
+ <div class="card mb-3">
134
+ <div class="card-body">
135
+ <h5 class="card-title">Re-paw-sitory Summaries</h5>
136
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864612.png" class="cat-corner cat-top-right">
137
+ <div id="summaries"></div>
138
+ </div>
139
+ </div>
140
+
141
+ <!-- New card for CLI setup instructions -->
142
+ <div class="card mb-3">
143
+ <div class="card-body">
144
+ <h5 class="card-title">Purr-fect Setup Instructions</h5>
145
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864503.png" class="cat-corner cat-top-right">
146
+ <div id="cliSetup" class="cli-setup"></div>
147
+ </div>
148
+ </div>
149
+
150
+ <div class="card mb-3">
151
+ <div class="card-body">
152
+ <h5 class="card-title">Key In-sights</h5>
153
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864493.png" class="cat-corner cat-top-right">
154
+ <div id="insights"></div>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="card mb-3">
159
+ <div class="card-body">
160
+ <h5 class="card-title">Meow-commendations</h5>
161
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864475.png" class="cat-corner cat-top-right">
162
+ <div id="recommendations"></div>
163
+ </div>
164
+ </div>
165
+
166
+ <div class="card mb-3">
167
+ <div class="card-body">
168
+ <h5 class="card-title">Next Expurr-loration Area</h5>
169
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864470.png" class="cat-corner cat-top-right">
170
+ <div id="nextArea"></div>
171
+ </div>
172
+ </div>
173
+
174
+ <div class="card mb-3">
175
+ <div class="card-body">
176
+ <h5 class="card-title">Quiz Questions (Cat-egories)</h5>
177
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864504.png" class="cat-corner cat-top-right">
178
+ <div id="questions"></div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- PR Review Tab -->
185
+ <div class="tab-pane fade" id="pr-panel" role="tabpanel" aria-labelledby="pr-tab">
186
+ <div class="card mb-4">
187
+ <div class="card-body">
188
+ <h5 class="card-title">Review GitHub Pull Request</h5>
189
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864490.png" class="cat-corner cat-top-right">
190
+ <form id="prForm">
191
+ <div class="mb-3">
192
+ <label for="pr_url" class="form-label">GitHub Pull Request URL <i class="fas fa-code-branch" style="color: var(--cat-accent);"></i></label>
193
+ <input type="url" class="form-control" id="pr_url" name="pr_url"
194
+ placeholder="https://github.com/username/repository/pull/123" required>
195
+ <div class="form-text">Enter the URL of a GitHub PR you want to review</div>
196
+ </div>
197
+
198
+ <div class="row mb-3">
199
+ <div class="col-md-6">
200
+ <label for="max_files" class="form-label">Maximum Files to Analyze <i class="fas fa-file-code" style="color: var(--cat-primary);"></i></label>
201
+ <select class="form-select" id="max_files" name="max_files">
202
+ <option value="10" selected>10 files</option>
203
+ <option value="25" >25 files</option>
204
+ <option value="50">50 files</option>
205
+ <option value="100">100 files</option>
206
+ </select>
207
+ <div class="form-text">Large PRs may take longer to analyze</div>
208
+ </div>
209
+ <div class="col-md-6">
210
+ <label for="file_type_filter" class="form-label">Filter by File Type <i class="fas fa-filter" style="color: var(--cat-accent);"></i></label>
211
+ <select class="form-select" id="file_type_filter" name="file_type_filter">
212
+ <option value="all" selected>All Code Files</option>
213
+ <option value="python">Python (.py)</option>
214
+ <option value="javascript">JavaScript (.js, .jsx, .ts, .tsx)</option>
215
+ <option value="web">Web (HTML, CSS)</option>
216
+ <option value="data">Data Files (JSON, YAML, SQL)</option>
217
+ <option value="docs">Documentation (MD, TXT)</option>
218
+ </select>
219
+ </div>
220
+ </div>
221
+
222
+ <button type="submit" class="btn btn-primary">
223
+ <i class="fas fa-paw me-2"></i> Fur-fect Code Review
224
+ </button>
225
+ </form>
226
+ </div>
227
+ </div>
228
+
229
+ <div id="pr-loading" class="loading">
230
+ <img src="https://media.giphy.com/media/mlvseq9yvZhba/giphy.gif" alt="Cat reviewing code animation">
231
+ <p>Reviewing PR... Our senior developer cat is analyzing your code changes!</p>
232
+ </div>
233
+
234
+ <div id="pr-results" class="d-none">
235
+ <div class="card mb-3">
236
+ <div class="card-body">
237
+ <h5 class="card-title">Pull Request Overview</h5>
238
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864521.png" class="cat-corner cat-top-right">
239
+ <div id="pr-overview">
240
+ <div class="d-flex justify-content-between align-items-center mb-3">
241
+ <div>
242
+ <h6 id="pr-title"></h6>
243
+ <p class="text-muted mb-0">Created by <span id="pr-user"></span></p>
244
+ </div>
245
+ <div>
246
+ <span class="badge bg-primary rounded-pill" id="pr-files-count"></span>
247
+ <span class="badge bg-info rounded-pill" id="pr-total-files"></span>
248
+ </div>
249
+ </div>
250
+ <div class="d-flex mt-2">
251
+ <div class="badge bg-secondary me-2">Target: <span id="pr-target"></span></div>
252
+ <div class="badge bg-info me-2">Source: <span id="pr-source"></span></div>
253
+ </div>
254
+ <div class="alert alert-warning mt-3" id="pr-file-limit-warning">
255
+ <i class="fas fa-exclamation-triangle me-2"></i>
256
+ <span id="pr-file-limit-message"></span>
257
+ </div>
258
+ </div>
259
+ </div>
260
+ </div>
261
+
262
+ <div class="card mb-3">
263
+ <div class="card-body">
264
+ <h5 class="card-title">Files Analyzed</h5>
265
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864464.png" class="cat-corner cat-top-right">
266
+ <div id="pr-files-analyzed"></div>
267
+ </div>
268
+ </div>
269
+
270
+ <div class="card mb-3">
271
+ <div class="card-body">
272
+ <h5 class="card-title">Senior Developer Review</h5>
273
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864464.png" class="cat-corner cat-top-right">
274
+ <div id="pr-review" class="review-content"></div>
275
+ </div>
276
+ </div>
277
+ </div>
278
+ </div>
279
+
280
+ <!-- GitHub Login Tab -->
281
+ <div class="tab-pane fade" id="login-panel" role="tabpanel" aria-labelledby="login-tab">
282
+ <div class="card mb-4">
283
+ <div class="card-body">
284
+ <h5 class="card-title">GitHub Authentication</h5>
285
+ <img src="https://cdn-icons-png.flaticon.com/512/1864/1864509.png" class="cat-corner cat-top-right">
286
+
287
+ <div id="login-status-container" class="mb-4 d-none">
288
+ <div class="alert alert-success" id="login-success">
289
+ <i class="fas fa-check-circle me-2"></i>
290
+ <span>You are logged in to GitHub as <strong id="github-username">username</strong></span>
291
+ </div>
292
+ <button id="logout-button" class="btn btn-outline-secondary">
293
+ <i class="fas fa-sign-out-alt me-2"></i> Logout from GitHub
294
+ </button>
295
+ </div>
296
+
297
+ <form id="github-login-form">
298
+ <div class="mb-3">
299
+ <label for="github_username" class="form-label">GitHub Username <i class="fas fa-user" style="color: var(--cat-accent);"></i></label>
300
+ <input type="text" class="form-control" id="github_username" name="github_username"
301
+ placeholder="Your GitHub username" required>
302
+ </div>
303
+ <div class="mb-3">
304
+ <label for="github_token" class="form-label">Personal Access Token <i class="fas fa-key" style="color: var(--cat-primary);"></i></label>
305
+ <input type="password" class="form-control" id="github_token" name="github_token"
306
+ placeholder="Your GitHub personal access token" required>
307
+ <div class="form-text">
308
+ We recommend using a <a href="https://github.com/settings/tokens" target="_blank">personal access token</a> with 'repo' scope.
309
+ <br>Your credentials are only stored in your browser's local storage and are never sent to our servers.
310
+ </div>
311
+ </div>
312
+ <button type="submit" class="btn btn-primary">
313
+ <i class="fas fa-sign-in-alt me-2"></i> Connect to GitHub
314
+ </button>
315
+ </form>
316
+
317
+ <div class="mt-4">
318
+ <h6 class="mb-3"><i class="fas fa-info-circle me-2" style="color: var(--cat-accent);"></i>How to create a Personal Access Token:</h6>
319
+ <ol>
320
+ <li>Go to your GitHub <a href="https://github.com/settings/tokens" target="_blank">Personal Access Tokens</a> page</li>
321
+ <li>Click "Generate new token" (classic)</li>
322
+ <li>Give it a name (e.g., "Purr-fect Code Analyzer")</li>
323
+ <li>Select the <strong>repo</strong> scope to access private repositories</li>
324
+ <li>Click "Generate token" and copy the token value</li>
325
+ <li>Paste the token in the field above</li>
326
+ </ol>
327
+ <div class="alert alert-warning">
328
+ <i class="fas fa-exclamation-triangle me-2"></i>
329
+ For security, tokens are only shown once when created. If you lose it, you'll need to generate a new one.
330
+ </div>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ </div>
335
+ </div>
336
+
337
+ <footer class="text-center mt-5">
338
+ <p>Created with <i class="fas fa-heart"></i> by the Coding Cats Team</p>
339
+ <div>
340
+ <i class="fas fa-cat me-2"></i>
341
+ <i class="fas fa-paw me-2"></i>
342
+ <i class="fas fa-cat me-2"></i>
343
+ <i class="fas fa-paw me-2"></i>
344
+ <i class="fas fa-cat"></i>
345
+ </div>
346
+ </footer>
347
+ </div>
348
+
349
+ <!-- Add Bootstrap JS and needed dependencies -->
350
+ <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
351
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
352
+
353
+ <script>
354
+ // GitHub Authentication Utilities - defined early to make them available everywhere
355
+ // Function to get stored GitHub credentials
356
+ function getGitHubCredentials() {
357
+ return {
358
+ username: localStorage.getItem('github_username'),
359
+ token: localStorage.getItem('github_token')
360
+ };
361
+ }
362
+
363
+ // Update all API requests to include GitHub credentials if available
364
+ function addAuthToRequest(requestData) {
365
+ const credentials = getGitHubCredentials();
366
+
367
+ if (credentials.username && credentials.token) {
368
+ return {
369
+ ...requestData,
370
+ github_auth: {
371
+ username: credentials.username,
372
+ token: credentials.token
373
+ }
374
+ };
375
+ }
376
+
377
+ return requestData;
378
+ }
379
+
380
+ // Check if there are stored GitHub credentials
381
+ function checkStoredCredentials() {
382
+ const storedUsername = localStorage.getItem('github_username');
383
+ const storedToken = localStorage.getItem('github_token');
384
+
385
+ if (storedUsername && storedToken) {
386
+ // Display logged-in status
387
+ document.getElementById('github-username').textContent = storedUsername;
388
+ document.getElementById('login-status-container').classList.remove('d-none');
389
+ document.getElementById('github-login-form').classList.add('d-none');
390
+
391
+ // Update login tab to show login status
392
+ const loginTab = document.getElementById('login-tab');
393
+ loginTab.innerHTML = `<i class="fas fa-user-check me-2"></i>${storedUsername}`;
394
+
395
+ return { username: storedUsername, token: storedToken };
396
+ } else {
397
+ // Show login form
398
+ document.getElementById('login-status-container').classList.add('d-none');
399
+ document.getElementById('github-login-form').classList.remove('d-none');
400
+
401
+ // Reset login tab text
402
+ const loginTab = document.getElementById('login-tab');
403
+ loginTab.innerHTML = `<i class="fas fa-sign-in-alt me-2"></i>GitHub Login`;
404
+
405
+ return null;
406
+ }
407
+ }
408
+
409
+ // Repository Analysis Form Handler
410
+ document.getElementById('repoForm').addEventListener('submit', async function(e) {
411
+ e.preventDefault();
412
+
413
+ const repoUrl = document.getElementById('repo_url').value;
414
+ const goal = document.getElementById('goal').value;
415
+ const persona = document.getElementById('persona').value;
416
+
417
+ console.log("Repository Analysis form submitted with URL:", repoUrl);
418
+
419
+ // Show loading
420
+ document.getElementById('loading').style.display = 'block';
421
+ document.getElementById('results').classList.add('d-none');
422
+
423
+ try {
424
+ // Get GitHub credentials if available
425
+ const credentials = getGitHubCredentials();
426
+
427
+ // Prepare the request data with GitHub authentication
428
+ const requestData = {
429
+ repo_url: repoUrl,
430
+ goal: goal,
431
+ persona: persona
432
+ };
433
+
434
+ // Add GitHub auth if available
435
+ if (credentials.username && credentials.token) {
436
+ requestData.github_auth = {
437
+ username: credentials.username,
438
+ token: credentials.token
439
+ };
440
+ console.log("Adding GitHub auth to Repository Analysis request");
441
+ }
442
+
443
+ console.log("Sending request to /workflow API with data:", JSON.stringify(requestData));
444
+
445
+ const response = await fetch('/workflow', {
446
+ method: 'POST',
447
+ headers: {
448
+ 'Content-Type': 'application/json',
449
+ },
450
+ body: JSON.stringify(requestData),
451
+ });
452
+
453
+ console.log("Received response from /workflow API:", response.status);
454
+
455
+ const data = await response.json();
456
+ console.log("Response data:", data);
457
+
458
+ if (response.ok) {
459
+ // Display summaries
460
+ const summariesDiv = document.getElementById('summaries');
461
+ summariesDiv.innerHTML = '';
462
+
463
+ Object.entries(data.summaries).forEach(([filename, summary]) => {
464
+ summariesDiv.innerHTML += `
465
+ <div class="mb-3">
466
+ <h6><i class="fas fa-file-code me-2" style="color: var(--cat-primary);"></i>${filename}</h6>
467
+ <p>${summary}</p>
468
+ </div>
469
+ `;
470
+ });
471
+
472
+ // Display CLI setup instructions
473
+ const setupDiv = document.getElementById('cliSetup');
474
+ if (data.cli_setup && data.cli_setup.trim().length > 0) {
475
+ setupDiv.innerHTML = `<pre class="cli-instructions">${data.cli_setup}</pre>`;
476
+ } else {
477
+ setupDiv.innerHTML = `<p class="text-muted">No setup instructions available for this repository.</p>`;
478
+ }
479
+
480
+ // Display insights
481
+ document.getElementById('insights').innerHTML = `<ul class="paw-list">${data.insights.split('\n').map(insight =>
482
+ `<li>${insight}</li>`).join('')}</ul>`;
483
+
484
+ // Display recommendations
485
+ document.getElementById('recommendations').innerHTML = `<ul class="paw-list">${data.recommendations.split('\n').map(rec =>
486
+ `<li>${rec}</li>`).join('')}</ul>`;
487
+
488
+ // Display next area
489
+ document.getElementById('nextArea').innerHTML = `<p><i class="fas fa-map-marker-alt me-2" style="color: var(--cat-primary);"></i>${data.next_area}</p>`;
490
+
491
+ // Display questions
492
+ document.getElementById('questions').innerHTML = `<pre>${data.questions}</pre>`;
493
+
494
+ // Show results
495
+ document.getElementById('results').classList.remove('d-none');
496
+
497
+ // Scroll to results
498
+ document.getElementById('results').scrollIntoView({ behavior: 'smooth' });
499
+
500
+ // Add a cat emoji to the end of each section
501
+ const randomCatEmojis = ['🐱', '😸', '😹', '😻', '😽', 'πŸ™€', '😿', '😾', '🐈', '🐾'];
502
+ document.querySelectorAll('#results .card').forEach(card => {
503
+ const randomEmoji = randomCatEmojis[Math.floor(Math.random() * randomCatEmojis.length)];
504
+ const cardBody = card.querySelector('.card-body');
505
+ const emojiSpan = document.createElement('span');
506
+ emojiSpan.style.position = 'absolute';
507
+ emojiSpan.style.bottom = '10px';
508
+ emojiSpan.style.right = '10px';
509
+ emojiSpan.style.fontSize = '24px';
510
+ emojiSpan.textContent = randomEmoji;
511
+ cardBody.appendChild(emojiSpan);
512
+ });
513
+
514
+ // Store repository data for chat functionality
515
+ if (window.updateRepoData) {
516
+ window.updateRepoData(data);
517
+ }
518
+ } else {
519
+ alert('Error: ' + (data.error || 'Failed to analyze repository'));
520
+ }
521
+ } catch (error) {
522
+ console.error('Error:', error);
523
+ alert('An error occurred. Please try again.');
524
+ } finally {
525
+ // Hide loading
526
+ document.getElementById('loading').style.display = 'none';
527
+ }
528
+ });
529
+
530
+ // Add some cat fun
531
+ document.addEventListener('DOMContentLoaded', function() {
532
+ // Create a small cat that follows the mouse
533
+ const cat = document.createElement('div');
534
+ cat.innerHTML = '🐈';
535
+ cat.style.position = 'fixed';
536
+ cat.style.zIndex = '1000';
537
+ cat.style.fontSize = '24px';
538
+ cat.style.pointerEvents = 'none';
539
+ cat.style.transition = 'transform 0.2s ease-out';
540
+ document.body.appendChild(cat);
541
+
542
+ let lastMouseX = 0;
543
+ let lastMouseY = 0;
544
+
545
+ document.addEventListener('mousemove', function(e) {
546
+ lastMouseX = e.clientX;
547
+ lastMouseY = e.clientY;
548
+
549
+ // Position the cat slightly behind the cursor
550
+ cat.style.left = (lastMouseX - 30) + 'px';
551
+ cat.style.top = (lastMouseY - 10) + 'px';
552
+
553
+ // Flip the cat if moving left
554
+ if (e.movementX < 0) {
555
+ cat.style.transform = 'scaleX(-1)';
556
+ } else if (e.movementX > 0) {
557
+ cat.style.transform = 'scaleX(1)';
558
+ }
559
+ });
560
+
561
+ // Handle the Repository Agent form submission
562
+ const agentRepoForm = document.getElementById('agentRepoForm');
563
+ const agentLoading = document.getElementById('agent-loading');
564
+ const agentInterface = document.getElementById('agent-interface');
565
+
566
+ // Update the agentRepoForm to include GitHub auth
567
+ agentRepoForm.addEventListener('submit', async function(e) {
568
+ e.preventDefault();
569
+
570
+ const repoUrl = document.getElementById('agent_repo_url').value;
571
+
572
+ if (!repoUrl) {
573
+ alert('Please enter a valid GitHub repository URL');
574
+ return;
575
+ }
576
+
577
+ // Show loading
578
+ agentLoading.style.display = 'block';
579
+ agentInterface.classList.add('d-none');
580
+
581
+ try {
582
+ // Get GitHub credentials if available
583
+ const requestData = {
584
+ repo_url: repoUrl,
585
+ goal: "Chat with repository agent",
586
+ persona: "Developer"
587
+ };
588
+
589
+ // Add GitHub auth if available
590
+ const authData = addAuthToRequest(requestData);
591
+
592
+ // Fetch repository data
593
+ const response = await fetch('/workflow', {
594
+ method: 'POST',
595
+ headers: {
596
+ 'Content-Type': 'application/json',
597
+ },
598
+ body: JSON.stringify(authData),
599
+ });
600
+
601
+ const data = await response.json();
602
+
603
+ if (response.ok) {
604
+ // Store repository data for chat functionality
605
+ repoData = {
606
+ url: repoUrl,
607
+ content: data.repo_content || {},
608
+ metadata: data.repo_metadata || {},
609
+ summaries: data.summaries || {},
610
+ insights: data.insights || ''
611
+ };
612
+
613
+ // Show the chat interface
614
+ agentInterface.classList.remove('d-none');
615
+
616
+ // Scroll to chat interface
617
+ agentInterface.scrollIntoView({ behavior: 'smooth' });
618
+
619
+ // Clear any previous messages
620
+ if (chatMessages) {
621
+ chatMessages.innerHTML = '';
622
+ }
623
+
624
+ // Add a welcome message
625
+ setTimeout(() => {
626
+ addMessage(`😺 Meow! I'm connected to ${data.repo_metadata.name || 'your repository'}. Ask me anything about the code structure, setup, or functionality!`, false);
627
+ }, 500);
628
+
629
+ // Add a cat emoji to the chat card
630
+ const randomCatEmojis = ['🐱', '😸', '😹', '😻', '😽', 'πŸ™€', '😿', '😾', '🐈', '🐾'];
631
+ const randomEmoji = randomCatEmojis[Math.floor(Math.random() * randomCatEmojis.length)];
632
+ const cardBody = agentInterface.querySelector('.card-body');
633
+ const emojiSpan = document.createElement('span');
634
+ emojiSpan.style.position = 'absolute';
635
+ emojiSpan.style.bottom = '10px';
636
+ emojiSpan.style.right = '10px';
637
+ emojiSpan.style.fontSize = '24px';
638
+ emojiSpan.textContent = randomEmoji;
639
+ cardBody.appendChild(emojiSpan);
640
+
641
+ // Resize the chat container
642
+ resizeChatContainer();
643
+ } else {
644
+ alert('Error: ' + (data.error || 'Failed to connect to repository'));
645
+ }
646
+ } catch (error) {
647
+ console.error('Error:', error);
648
+ alert('An error occurred. Please try again.');
649
+ } finally {
650
+ // Hide loading
651
+ agentLoading.style.display = 'none';
652
+ }
653
+ });
654
+
655
+ // Chat functionality
656
+ const chatInput = document.getElementById('chat-input');
657
+ const chatSubmit = document.getElementById('chat-submit');
658
+ const chatMessages = document.getElementById('chat-messages');
659
+ const chatLoading = document.getElementById('chat-loading');
660
+
661
+ // Initialize repository data storage
662
+ let repoData = {
663
+ url: '',
664
+ content: {},
665
+ metadata: {},
666
+ summaries: {},
667
+ insights: ''
668
+ };
669
+
670
+ // Store the data from workflow response for use in chat
671
+ document.getElementById('repoForm').addEventListener('submit', function() {
672
+ // This will be populated when the workflow response comes back
673
+ setTimeout(() => {
674
+ const repoUrl = document.getElementById('repo_url').value;
675
+ repoData.url = repoUrl;
676
+ }, 100);
677
+ });
678
+
679
+ // Send a message when the send button is clicked
680
+ chatSubmit.addEventListener('click', sendMessage);
681
+
682
+ // Also send a message when Enter is pressed in the input field
683
+ chatInput.addEventListener('keypress', function(e) {
684
+ if (e.key === 'Enter') {
685
+ sendMessage();
686
+ }
687
+ });
688
+
689
+ function formatTimestamp() {
690
+ const now = new Date();
691
+ return now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
692
+ }
693
+
694
+ function addMessage(text, isUser = false) {
695
+ const messageElement = document.createElement('div');
696
+ messageElement.classList.add('chat-message');
697
+ messageElement.classList.add(isUser ? 'user-message' : 'bot-message');
698
+
699
+ // Format message text to handle code blocks and line breaks
700
+ let formattedText = text;
701
+
702
+ // Convert markdown code blocks to HTML
703
+ formattedText = formattedText.replace(/```([^`]+)```/g, '<pre><code>$1</code></pre>');
704
+
705
+ // Convert inline code to HTML
706
+ formattedText = formattedText.replace(/`([^`]+)`/g, '<code>$1</code>');
707
+
708
+ // Convert line breaks to <br>
709
+ formattedText = formattedText.replace(/\n/g, '<br>');
710
+
711
+ messageElement.innerHTML = `
712
+ ${formattedText}
713
+ <span class="message-time">${formatTimestamp()}</span>
714
+ `;
715
+
716
+ chatMessages.appendChild(messageElement);
717
+
718
+ // Scroll to the bottom of the chat
719
+ chatMessages.scrollTop = chatMessages.scrollHeight;
720
+ }
721
+
722
+ async function sendMessage() {
723
+ const message = chatInput.value.trim();
724
+ if (!message) return;
725
+
726
+ // Add user message to chat
727
+ addMessage(message, true);
728
+
729
+ // Clear input field
730
+ chatInput.value = '';
731
+
732
+ // Show loading indicator
733
+ chatLoading.classList.remove('d-none');
734
+
735
+ // Collect data needed for the chat request
736
+ const repoUrl = repoData.url || document.getElementById('repo_url').value;
737
+
738
+ // Collect summaries from displayed content
739
+ const summariesDiv = document.getElementById('summaries');
740
+ const summaries = {};
741
+ if (summariesDiv) {
742
+ const summaryElements = summariesDiv.querySelectorAll('h6');
743
+ summaryElements.forEach(el => {
744
+ const filename = el.innerText;
745
+ const summary = el.nextElementSibling.innerText;
746
+ summaries[filename] = summary;
747
+ });
748
+ }
749
+
750
+ // Get insights
751
+ const insightsEl = document.getElementById('insights');
752
+ let insights = '';
753
+ if (insightsEl) {
754
+ insights = insightsEl.innerText;
755
+ }
756
+
757
+ try {
758
+ const response = await fetch('/chat', {
759
+ method: 'POST',
760
+ headers: {
761
+ 'Content-Type': 'application/json',
762
+ },
763
+ body: JSON.stringify({
764
+ question: message,
765
+ repo_url: repoUrl,
766
+ repo_content: repoData.content,
767
+ repo_metadata: repoData.metadata,
768
+ summaries: summaries,
769
+ insights: insights
770
+ }),
771
+ });
772
+
773
+ const data = await response.json();
774
+
775
+ if (response.ok) {
776
+ // Add bot response to chat
777
+ addMessage(data.answer, false);
778
+ } else {
779
+ // Add error message
780
+ addMessage('Sorry, I encountered an error: ' + (data.error || 'Unknown error'), false);
781
+ }
782
+ } catch (error) {
783
+ console.error('Error:', error);
784
+ addMessage('Sorry, I encountered a technical problem. Please try again.', false);
785
+ } finally {
786
+ // Hide loading indicator
787
+ chatLoading.classList.add('d-none');
788
+ }
789
+ }
790
+
791
+ // Update stored repo data when workflow completes
792
+ window.updateRepoData = function(data) {
793
+ repoData = {
794
+ url: document.getElementById('repo_url').value,
795
+ content: data.repo_content || {},
796
+ metadata: data.repo_metadata || {},
797
+ summaries: data.summaries || {},
798
+ insights: data.insights || ''
799
+ };
800
+
801
+ // Add a welcome message from Repo Cat after analysis
802
+ if (chatMessages && chatMessages.childElementCount === 0) {
803
+ setTimeout(() => {
804
+ addMessage('😺 Meow! I\'ve analyzed this repository and I\'m ready to answer your questions about it. Ask me anything about the code structure, setup, or functionality!', false);
805
+ }, 1000);
806
+ }
807
+ };
808
+
809
+ // Add chat container resize functionality
810
+ function resizeChatContainer() {
811
+ const chatContainer = document.getElementById('chat-container');
812
+ const chatMessages = document.getElementById('chat-messages');
813
+
814
+ if (!chatContainer || !chatMessages) return;
815
+
816
+ // Get viewport height and calculate appropriate height
817
+ const viewportHeight = window.innerHeight;
818
+
819
+ // Default to taking up 80% of viewport height minus space for headers
820
+ const headerHeight = document.querySelector('h1').offsetHeight +
821
+ document.querySelector('.nav-tabs').offsetHeight +
822
+ document.querySelector('#agent-panel .card').offsetHeight + 60; // Add padding
823
+
824
+ // Calculate the available height for chat
825
+ const availableHeight = viewportHeight - headerHeight;
826
+ const optimalHeight = Math.max(250, Math.min(availableHeight * 0.9, viewportHeight * 0.8));
827
+
828
+ // Apply height to chat container
829
+ chatContainer.style.height = `${optimalHeight}px`;
830
+
831
+ // Messages container should have some space for the input box
832
+ chatMessages.style.maxHeight = `${optimalHeight - 60}px`;
833
+ }
834
+
835
+ // Call resize function when messages are added
836
+ const originalAddMessage = addMessage;
837
+ addMessage = function(text, isUser = false) {
838
+ originalAddMessage(text, isUser);
839
+ resizeChatContainer();
840
+ };
841
+
842
+ // Call resize on window resize
843
+ window.addEventListener('resize', resizeChatContainer);
844
+
845
+ // Initial resize
846
+ setTimeout(resizeChatContainer, 100);
847
+
848
+ // GitHub Login Form Handling
849
+ const githubLoginForm = document.getElementById('github-login-form');
850
+ const loginStatusContainer = document.getElementById('login-status-container');
851
+ const logoutButton = document.getElementById('logout-button');
852
+ const githubUsername = document.getElementById('github-username');
853
+
854
+ // Check if there are stored GitHub credentials
855
+ function checkStoredCredentials() {
856
+ const storedUsername = localStorage.getItem('github_username');
857
+ const storedToken = localStorage.getItem('github_token');
858
+
859
+ if (storedUsername && storedToken) {
860
+ // Display logged-in status
861
+ githubUsername.textContent = storedUsername;
862
+ loginStatusContainer.classList.remove('d-none');
863
+ githubLoginForm.classList.add('d-none');
864
+
865
+ // Update login tab to show login status
866
+ const loginTab = document.getElementById('login-tab');
867
+ loginTab.innerHTML = `<i class="fas fa-user-check me-2"></i>${storedUsername}`;
868
+
869
+ return { username: storedUsername, token: storedToken };
870
+ } else {
871
+ // Show login form
872
+ loginStatusContainer.classList.add('d-none');
873
+ githubLoginForm.classList.remove('d-none');
874
+
875
+ // Reset login tab text
876
+ const loginTab = document.getElementById('login-tab');
877
+ loginTab.innerHTML = `<i class="fas fa-sign-in-alt me-2"></i>GitHub Login`;
878
+
879
+ return null;
880
+ }
881
+ }
882
+
883
+ // Handle GitHub login
884
+ githubLoginForm.addEventListener('submit', async function(e) {
885
+ e.preventDefault();
886
+
887
+ const username = document.getElementById('github_username').value;
888
+ const token = document.getElementById('github_token').value;
889
+
890
+ if (!username || !token) {
891
+ alert('Please enter both username and token');
892
+ return;
893
+ }
894
+
895
+ try {
896
+ // Verify the credentials by making a test API call
897
+ const response = await fetch('/verify_github_credentials', {
898
+ method: 'POST',
899
+ headers: {
900
+ 'Content-Type': 'application/json',
901
+ },
902
+ body: JSON.stringify({
903
+ github_username: username,
904
+ github_token: token
905
+ }),
906
+ });
907
+
908
+ const data = await response.json();
909
+
910
+ if (response.ok && data.valid) {
911
+ // Store credentials in browser's localStorage
912
+ localStorage.setItem('github_username', username);
913
+ localStorage.setItem('github_token', token);
914
+
915
+ // Update UI to show logged-in status
916
+ githubUsername.textContent = username;
917
+ loginStatusContainer.classList.remove('d-none');
918
+ githubLoginForm.classList.add('d-none');
919
+
920
+ // Update login tab to show login status
921
+ const loginTab = document.getElementById('login-tab');
922
+ loginTab.innerHTML = `<i class="fas fa-user-check me-2"></i>${username}`;
923
+
924
+ alert('Successfully connected to GitHub!');
925
+ } else {
926
+ alert('Invalid GitHub credentials. Please check your username and token.');
927
+ }
928
+ } catch (error) {
929
+ console.error('Error:', error);
930
+ alert('Error verifying GitHub credentials. Please try again.');
931
+ }
932
+ });
933
+
934
+ // Handle logout
935
+ logoutButton.addEventListener('click', function() {
936
+ // Clear stored credentials
937
+ localStorage.removeItem('github_username');
938
+ localStorage.removeItem('github_token');
939
+
940
+ // Update UI
941
+ loginStatusContainer.classList.add('d-none');
942
+ githubLoginForm.classList.remove('d-none');
943
+
944
+ // Reset form fields
945
+ document.getElementById('github_username').value = '';
946
+ document.getElementById('github_token').value = '';
947
+
948
+ // Reset login tab text
949
+ const loginTab = document.getElementById('login-tab');
950
+ loginTab.innerHTML = `<i class="fas fa-sign-in-alt me-2"></i>GitHub Login`;
951
+
952
+ alert('You have been logged out from GitHub.');
953
+ });
954
+
955
+ // Check for stored credentials on page load
956
+ document.addEventListener('DOMContentLoaded', function() {
957
+ checkStoredCredentials();
958
+ });
959
+
960
+ // Function to get stored GitHub credentials
961
+ function getGitHubCredentials() {
962
+ return {
963
+ username: localStorage.getItem('github_username'),
964
+ token: localStorage.getItem('github_token')
965
+ };
966
+ }
967
+
968
+ // Update all API requests to include GitHub credentials if available
969
+ function addAuthToRequest(requestData) {
970
+ const credentials = getGitHubCredentials();
971
+
972
+ if (credentials.username && credentials.token) {
973
+ return {
974
+ ...requestData,
975
+ github_auth: {
976
+ username: credentials.username,
977
+ token: credentials.token
978
+ }
979
+ };
980
+ }
981
+
982
+ return requestData;
983
+ }
984
+
985
+ // PR Review Form Handling
986
+ const prForm = document.getElementById('prForm');
987
+ const prLoading = document.getElementById('pr-loading');
988
+ const prResults = document.getElementById('pr-results');
989
+
990
+ prForm.addEventListener('submit', async function(e) {
991
+ e.preventDefault();
992
+
993
+ const prUrl = document.getElementById('pr_url').value;
994
+ const maxFiles = parseInt(document.getElementById('max_files').value);
995
+ const fileTypeFilter = document.getElementById('file_type_filter').value;
996
+
997
+ if (!prUrl) {
998
+ alert('Please enter a valid GitHub PR URL');
999
+ return;
1000
+ }
1001
+
1002
+ // Map the file type filter selection to actual file extensions
1003
+ let fileTypes = null;
1004
+ switch(fileTypeFilter) {
1005
+ case 'python':
1006
+ fileTypes = ['.py'];
1007
+ break;
1008
+ case 'javascript':
1009
+ fileTypes = ['.js', '.jsx', '.ts', '.tsx'];
1010
+ break;
1011
+ case 'web':
1012
+ fileTypes = ['.html', '.css'];
1013
+ break;
1014
+ case 'data':
1015
+ fileTypes = ['.json', '.yml', '.yaml', '.sql'];
1016
+ break;
1017
+ case 'docs':
1018
+ fileTypes = ['.md', '.txt'];
1019
+ break;
1020
+ // 'all' or default: null = all code files
1021
+ }
1022
+
1023
+ // Show loading
1024
+ prLoading.style.display = 'block';
1025
+ prResults.classList.add('d-none');
1026
+
1027
+ try {
1028
+ // Scroll to the loading indicator
1029
+ prLoading.scrollIntoView({ behavior: 'smooth' });
1030
+
1031
+ // Prepare the request data
1032
+ const requestData = {
1033
+ pr_url: prUrl,
1034
+ max_files: maxFiles,
1035
+ file_types: fileTypes
1036
+ };
1037
+
1038
+ // Add GitHub auth if available
1039
+ const authData = addAuthToRequest(requestData);
1040
+
1041
+ const response = await fetch('/review_pr', {
1042
+ method: 'POST',
1043
+ headers: {
1044
+ 'Content-Type': 'application/json',
1045
+ },
1046
+ body: JSON.stringify(authData),
1047
+ });
1048
+
1049
+ const data = await response.json();
1050
+
1051
+ if (response.ok) {
1052
+ // Display PR details
1053
+ document.getElementById('pr-title').textContent = data.pr_title;
1054
+ document.getElementById('pr-user').textContent = data.pr_user;
1055
+ document.getElementById('pr-target').textContent = data.target_branch;
1056
+ document.getElementById('pr-source').textContent = data.source_branch;
1057
+ document.getElementById('pr-files-count').textContent = data.changed_files_count + ' files analyzed';
1058
+ document.getElementById('pr-total-files').textContent = data.total_file_count + ' total files in PR';
1059
+
1060
+ // Show or hide the file limit warning
1061
+ const fileWarning = document.getElementById('pr-file-limit-warning');
1062
+ if (data.total_file_count > data.changed_files_count) {
1063
+ fileWarning.style.display = 'block';
1064
+ fileWarning.querySelector('#pr-file-limit-message').textContent =
1065
+ `This PR contains ${data.total_file_count} files, but only ${data.changed_files_count} were analyzed due to size limits. ` +
1066
+ `Try filtering by file type or increase the maximum files limit to get more comprehensive feedback.`;
1067
+ } else {
1068
+ fileWarning.style.display = 'none';
1069
+ }
1070
+
1071
+ // Display the list of analyzed files
1072
+ const filesAnalyzedDiv = document.getElementById('pr-files-analyzed');
1073
+ if (data.analyzed_files && data.analyzed_files.length > 0) {
1074
+ // Group files by directory for better organization
1075
+ const filesByDir = {};
1076
+ data.analyzed_files.forEach(file => {
1077
+ const lastSlash = file.lastIndexOf('/');
1078
+ const dir = lastSlash > 0 ? file.substring(0, lastSlash) : '/';
1079
+ if (!filesByDir[dir]) {
1080
+ filesByDir[dir] = [];
1081
+ }
1082
+ filesByDir[dir].push(file);
1083
+ });
1084
+
1085
+ let filesHtml = '';
1086
+ Object.entries(filesByDir).forEach(([dir, files]) => {
1087
+ filesHtml += `<div class="mb-2"><strong>${dir || 'Root'}</strong>: `;
1088
+ filesHtml += files.map(file => {
1089
+ const fileName = file.substring(file.lastIndexOf('/') + 1);
1090
+ return `<span class="badge bg-light text-dark me-1">${fileName}</span>`;
1091
+ }).join('');
1092
+ filesHtml += '</div>';
1093
+ });
1094
+
1095
+ filesAnalyzedDiv.innerHTML = filesHtml;
1096
+ } else {
1097
+ filesAnalyzedDiv.innerHTML = '<p class="text-muted">No files were analyzed. Try changing the file type filter.</p>';
1098
+ }
1099
+
1100
+ // Display review
1101
+ const reviewContent = document.getElementById('pr-review');
1102
+
1103
+ // Format the review content with markdown-like syntax
1104
+ let formattedReview = data.review;
1105
+
1106
+ // Convert markdown headers to HTML
1107
+ formattedReview = formattedReview.replace(/## ([^\n]+)/g, '<h4>$1</h4>');
1108
+
1109
+ // Convert markdown lists to HTML
1110
+ formattedReview = formattedReview.replace(/- ([^\n]+)/g, '<li>$1</li>');
1111
+ formattedReview = formattedReview.replace(/<li>/g, '<ul><li>');
1112
+ formattedReview = formattedReview.replace(/<\/li>\n/g, '</li></ul>\n');
1113
+
1114
+ // Convert code blocks
1115
+ formattedReview = formattedReview.replace(/```([^`]+)```/g, '<pre><code>$1</code></pre>');
1116
+
1117
+ // Convert inline code
1118
+ formattedReview = formattedReview.replace(/`([^`]+)`/g, '<code>$1</code>');
1119
+
1120
+ // Handle line breaks
1121
+ formattedReview = formattedReview.replace(/\n/g, '<br>');
1122
+
1123
+ reviewContent.innerHTML = formattedReview;
1124
+
1125
+ // Show results
1126
+ prResults.classList.remove('d-none');
1127
+
1128
+ // Scroll to results
1129
+ prResults.scrollIntoView({ behavior: 'smooth' });
1130
+
1131
+ // Add a cat emoji to the end of each section
1132
+ const randomCatEmojis = ['🐱', '😸', '😹', '😻', '😽', 'πŸ™€', '😿', '😾', '🐈', '🐾'];
1133
+ document.querySelectorAll('#pr-results .card').forEach(card => {
1134
+ const randomEmoji = randomCatEmojis[Math.floor(Math.random() * randomCatEmojis.length)];
1135
+ const cardBody = card.querySelector('.card-body');
1136
+ const emojiSpan = document.createElement('span');
1137
+ emojiSpan.style.position = 'absolute';
1138
+ emojiSpan.style.bottom = '10px';
1139
+ emojiSpan.style.right = '10px';
1140
+ emojiSpan.style.fontSize = '24px';
1141
+ emojiSpan.textContent = randomEmoji;
1142
+ cardBody.appendChild(emojiSpan);
1143
+ });
1144
+ } else {
1145
+ alert('Error: ' + (data.error || 'Failed to review PR'));
1146
+ }
1147
+ } catch (error) {
1148
+ console.error('Error:', error);
1149
+ alert('An error occurred. Please try again.');
1150
+ } finally {
1151
+ // Hide loading
1152
+ prLoading.style.display = 'none';
1153
+ }
1154
+ });
1155
+ });
1156
+ </script>
1157
+ </body>
1158
+ </html>