acecalisto3 commited on
Commit
fae9bd0
·
verified ·
1 Parent(s): c8af669

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +222 -177
app.py CHANGED
@@ -1,132 +1,71 @@
1
- import os
2
- import shutil
3
- import logging
4
- import time
5
- import requests
6
- import gradio as gr
7
- import atexit
8
  import subprocess
9
- from urllib.parse import urlparse, quote
10
- import webbrowser
11
- from datetime import datetime
12
- from typing import List, Dict, Any
13
- from abc import ABC, abstractmethod
14
- from enum import Enum
15
- from dataclasses import dataclass
16
- from concurrent.futures import ThreadPoolExecutor
17
- import base64
18
-
19
- # Constants
20
- INPUT_DIRECTORY = 'input'
21
- OUTPUT_DIRECTORY = 'output'
22
- LOGS_DIRECTORY = 'logs'
23
- RESOLUTIONS_DIRECTORY = 'resolutions'
24
- REPOS_DIRECTORY = 'repos'
25
-
26
- # Set up logging
27
- def initialize_logger() -> logging.Logger:
28
- log_file = f"{LOGS_DIRECTORY}/github_bot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
29
- logging.basicConfig(
30
- level=logging.INFO,
31
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
32
- handlers=[
33
- logging.FileHandler(log_file),
34
- logging.StreamHandler()
35
- ]
36
- )
37
- return logging.getLogger(__name__)
38
-
39
- # Initialize environment
40
- def initialize_environment():
41
- directories = [LOGS_DIRECTORY, RESOLUTIONS_DIRECTORY, REPOS_DIRECTORY, INPUT_DIRECTORY, OUTPUT_DIRECTORY]
42
- for directory in directories:
43
- os.makedirs(directory, exist_ok=True)
44
-
45
- # GitHub API handler
46
- class GitHubAPI:
47
- def __init__(self, token: str):
48
- self.token = token
49
- self.headers = {
50
- 'Authorization': f'token {token}',
51
- 'Accept': 'application/vnd.github.v3+json'
52
- }
53
- self.base_url = "https://api.github.com"
54
-
55
- def get_repository(self, owner: str, repo: str) -> Dict:
56
  try:
57
- response = requests.get(f"{self.base_url}/repos/{owner}/{repo}", headers=self.headers)
58
- response.raise_for_status()
59
- return response.json()
60
- except requests.HTTPError as e:
61
- logger.error(f"HTTP error getting repository info for {owner}/{repo}: {str(e)}")
62
- raise
 
 
 
63
  except Exception as e:
64
- logger.error(f"Error getting repository info: {str(e)}")
65
- raise
66
 
67
- def get_issues(self, owner: str, repo: str, state: str = 'open') -> List[Dict]:
68
- try:
69
- response = requests.get(f"{self.base_url}/repos/{owner}/{repo}/issues", headers=self.headers, params={'state': state})
70
- response.raise_for_status()
71
- issues = response.json()
72
- return [issue for issue in issues if 'pull_request' not in issue]
73
- except Exception as e:
74
- logger.error(f"Error fetching issues for repository {owner}/{repo}: {str(e)}")
75
- return []
76
-
77
- # Enum for Issue Severity
78
- class IssueSeverity(Enum):
79
- CRITICAL = 5
80
- HIGH = 4
81
- MEDIUM = 3
82
- LOW = 2
83
- TRIVIAL = 1
84
-
85
- @dataclass
86
- class CodeContext:
87
- file_path: str
88
- content: str
89
- language: str
90
-
91
- class AIProvider(ABC):
92
- @abstractmethod
93
- def analyze_code(self, content: str) -> str:
94
- pass
95
-
96
- @abstractmethod
97
- def generate_solution(self, context: str, issue: str) -> str:
98
- pass
99
-
100
- class CustomAIProvider(AIProvider):
101
- def __init__(self, api_base: str, api_key: str, model: str = "gpt-3.5-turbo"):
102
- self.api_base = api_base
103
- self.api_key = api_key
104
- self.model = model
105
- self.headers = {
106
- "Authorization": f"Bearer {api_key}",
107
- "Content-Type": "application/json"
108
- }
109
 
110
- def analyze_code(self, content: str) -> str:
111
- payload = {
112
- "model": self.model,
113
- "messages": [{"role": "user", "content": f"Analyze this code:\n{content}"}]
114
- }
115
- response = requests.post(f"{self.api_base}/v1/chat/completions",
116
- headers = self.headers,
117
- json=payload)
118
- return response.json()["choices"][0]["message"]["content"]
119
-
120
- def generate_solution(self, context: str, issue: str) -> str:
121
- payload = {
122
- "model": self.model,
123
- "messages": [{"role": "user", "content": f"Context:\n{context}\nIssue:\n{issue}\nGenerate solution:"}]
124
- }
125
- response = requests.post(f"{self.api_base}/v1/chat/completions",
126
- headers=self.headers,
127
- json=payload)
128
- return response.json()["choices"][0]["message"]["content"]
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  class GitHubBot:
131
  def __init__(self, logger: logging.Logger):
132
  self.github_api = None
@@ -141,49 +80,135 @@ class GitHubBot:
141
  '.go': {'language': 'Go', 'parser': 'go'},
142
  '.rs': {'language': 'Rust', 'parser': 'rust'}
143
  }
 
 
144
 
145
  def initialize_api(self, token: str, ai_provider: AIProvider = None):
146
- self.github_api = GitHubAPI(token)
147
  self.ai_provider = ai_provider
148
-
149
- def crawl_issues(self, owner: str, repo: str) -> List[Dict]:
150
- issues = self.github_api.get_issues(owner, repo)
151
- return issues
152
-
153
- def replicate_error(self, issue: Dict) -> str:
154
- # Simulate the error in a controlled environment
155
- # This is a placeholder for actual error replication logic
156
- return f"Simulated error for issue: {issue['title']}"
 
 
 
 
 
 
157
 
158
  def resolve_issue(self, token: str, owner: str, repo: str, issue_number: int, resolution: str, forked_repo: str) -> str:
159
  try:
160
  if not self.ai_provider:
161
  raise ValueError("AI provider not initialized. Please initialize with an AI provider.")
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  # Get issue details
164
  issue_url = f"{self.github_api.base_url}/repos/{owner}/{repo}/issues/{issue_number}"
165
  response = requests.get(issue_url, headers=self.github_api.headers)
166
  response.raise_for_status()
167
  issue = response.json()
168
 
169
- # Replicate the error
170
- error_replication = self.replicate_error(issue)
171
-
172
  # Generate AI solution
173
- context = f"Repository: {owner}/{repo}\nIssue #{issue_number}: {issue['title']}\n\nError Replication:\n{error_replication}\n"
 
 
 
174
  ai_solution = self.ai_provider.generate_solution(context, issue['body'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- # Create bash script for resolution
177
- bash_script = f"""#!/bin/bash
178
- # Bash script to resolve issue #{issue_number} in {owner}/{repo}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
- echo "Starting resolution for issue #{issue_number}..."
181
- # Add commands to resolve the issue here
182
- # Example: git clone {forked_repo}
183
- # Example: cd {repo}
184
- # Example: {ai_solution}
185
 
186
- echo "Resolution complete."
 
 
 
 
 
 
 
 
 
 
 
 
187
  """
188
 
189
  script_file = f"{RESOLUTIONS_DIRECTORY}/resolve_issue_{issue_number}.sh"
@@ -202,26 +227,54 @@ echo "Resolution complete."
202
 
203
  def create_gradio_interface():
204
  with gr.Blocks() as demo:
205
- gr.Markdown("# GitHub Issue Resolver with AI Assistant")
 
206
 
207
- with gr.Row():
208
- token_input = gr.Textbox(label="GitHub Token", placeholder="Enter your GitHub token")
209
- repo_url_input = gr.Textbox(label="Repository URL", placeholder="Enter the repository URL (owner/repo)")
 
 
 
 
 
210
 
211
- with gr.Row():
212
- ai_api_base = gr.Textbox(label="AI API Base URL", placeholder="Enter your AI API base URL")
213
- ai_api_key = gr.Textbox(label="AI API Key", placeholder="Enter your AI API key")
 
214
 
215
- with gr.Row():
216
- issue_number_input = gr.Number(label="Issue Number", info="Enter the issue number")
217
  resolution_input = gr.Textbox(label="Resolution", placeholder="Describe the resolution for the issue", lines=4)
 
218
 
219
- forked_repo_input = gr.Textbox(label="Forked Repository URL", placeholder="Enter the forked repository URL")
 
 
220
 
221
- submit_button = gr.Button("Resolve Issue")
222
- result_output = gr.Textbox(label="Result", interactive=False)
223
 
224
- def on_submit(token, repo_url, ai_api_base, ai_api_key, issue_number, resolution, forked_repo):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  try:
226
  parts = repo_url.split('/')
227
  owner, repo = parts[-2], parts[-1]
@@ -230,30 +283,22 @@ def create_gradio_interface():
230
  ai_provider = CustomAIProvider(ai_api_base, ai_api_key)
231
  bot.initialize_api(token, ai_provider)
232
 
233
- # Crawl issues and resolve
234
- issues = bot.crawl_issues(owner, repo)
235
  result = bot.resolve_issue(token, owner, repo, int(issue_number), resolution, forked_repo)
236
  return result
237
  except Exception as e:
238
  return f"Error: {str(e)}"
239
 
240
- submit_button.click(
241
- on_submit,
 
 
 
 
 
 
242
  inputs=[token_input, repo_url_input, ai_api_base, ai_api_key,
243
  issue_number_input, resolution_input, forked_repo_input],
244
- outputs=result_output
245
  )
246
 
247
- return demo
248
-
249
- # Initialize the logger and environment
250
- logger = initialize_logger()
251
- initialize_environment()
252
-
253
- # Create the GitHub bot instance
254
- bot = GitHubBot(logger)
255
-
256
- # Launch the Gradio interface
257
- if __name__ == "__main__":
258
- demo = create_gradio_interface()
259
- demo.launch()
 
1
+ # Add these new imports
2
+ from typing import Optional, Union, List, Dict, Any, Tuple
 
 
 
 
 
3
  import subprocess
4
+ from pathlib import Path
5
+ import json
6
+ import tempfile
7
+ from datetime import datetime, timezone
8
+ import re
9
+
10
+ # Add these utility classes based on git-bob's architecture
11
+ class TerminalCommand:
12
+ @staticmethod
13
+ def execute(command: Union[str, List[str]], cwd: Optional[str] = None) -> Tuple[str, str, int]:
14
+ """
15
+ Execute a terminal command and return stdout, stderr, and return code
16
+ """
17
+ if isinstance(command, str):
18
+ command = command.split()
19
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  try:
21
+ process = subprocess.Popen(
22
+ command,
23
+ stdout=subprocess.PIPE,
24
+ stderr=subprocess.PIPE,
25
+ cwd=cwd,
26
+ text=True
27
+ )
28
+ stdout, stderr = process.communicate()
29
+ return stdout.strip(), stderr.strip(), process.returncode
30
  except Exception as e:
31
+ return "", str(e), 1
 
32
 
33
+ class GitUtilities:
34
+ def __init__(self, repo_path: str):
35
+ self.repo_path = Path(repo_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
+ def clone(self, url: str, branch: str = "main") -> bool:
38
+ """Clone a repository"""
39
+ _, stderr, code = TerminalCommand.execute(
40
+ f"git clone -b {branch} {url} {self.repo_path}"
41
+ )
42
+ return code == 0
43
+
44
+ def commit(self, message: str) -> bool:
45
+ """Create a commit with the given message"""
46
+ _, _, code = TerminalCommand.execute(
47
+ ["git", "commit", "-m", message],
48
+ str(self.repo_path)
49
+ )
50
+ return code == 0
 
 
 
 
 
51
 
52
+ def push(self, remote: str = "origin", branch: str = "main") -> bool:
53
+ """Push changes to remote"""
54
+ _, _, code = TerminalCommand.execute(
55
+ ["git", "push", remote, branch],
56
+ str(self.repo_path)
57
+ )
58
+ return code == 0
59
+
60
+ def create_branch(self, branch_name: str) -> bool:
61
+ """Create and checkout a new branch"""
62
+ _, _, code = TerminalCommand.execute(
63
+ ["git", "checkout", "-b", branch_name],
64
+ str(self.repo_path)
65
+ )
66
+ return code == 0
67
+
68
+ # Enhance the GitHubBot class with new features
69
  class GitHubBot:
70
  def __init__(self, logger: logging.Logger):
71
  self.github_api = None
 
80
  '.go': {'language': 'Go', 'parser': 'go'},
81
  '.rs': {'language': 'Rust', 'parser': 'rust'}
82
  }
83
+ self.git = None
84
+ self.temp_dir = None
85
 
86
  def initialize_api(self, token: str, ai_provider: AIProvider = None):
87
+ self.github_api = GitHubAPI(token, self.logger)
88
  self.ai_provider = ai_provider
89
+ self.temp_dir = tempfile.mkdtemp()
90
+ self.git = GitUtilities(self.temp_dir)
91
+
92
+ def create_pull_request(self, owner: str, repo: str, title: str, body: str, head: str, base: str = "main") -> Dict:
93
+ """Create a pull request"""
94
+ url = f"{self.github_api.base_url}/repos/{owner}/{repo}/pulls"
95
+ data = {
96
+ "title": title,
97
+ "body": body,
98
+ "head": head,
99
+ "base": base
100
+ }
101
+ response = requests.post(url, headers=self.github_api.headers, json=data)
102
+ response.raise_for_status()
103
+ return response.json()
104
 
105
  def resolve_issue(self, token: str, owner: str, repo: str, issue_number: int, resolution: str, forked_repo: str) -> str:
106
  try:
107
  if not self.ai_provider:
108
  raise ValueError("AI provider not initialized. Please initialize with an AI provider.")
109
 
110
+ # Create a unique branch name for this fix
111
+ branch_name = f"fix/issue-{issue_number}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
112
+
113
+ # Clone repository and create new branch
114
+ if not self.git.clone(forked_repo):
115
+ raise Exception("Failed to clone repository")
116
+
117
+ if not self.git.create_branch(branch_name):
118
+ raise Exception("Failed to create branch")
119
+
120
+ # Get repository content and analyze
121
+ code_contexts = self._get_main_branch_content(owner, repo)
122
+
123
  # Get issue details
124
  issue_url = f"{self.github_api.base_url}/repos/{owner}/{repo}/issues/{issue_number}"
125
  response = requests.get(issue_url, headers=self.github_api.headers)
126
  response.raise_for_status()
127
  issue = response.json()
128
 
129
+ # Determine severity
130
+ severity = self._determine_severity(issue)
131
+
132
  # Generate AI solution
133
+ context = f"Repository: {owner}/{repo}\nIssue #{issue_number}: {issue['title']}\n\nCode contexts:\n"
134
+ for ctx in code_contexts[:3]:
135
+ context += f"\nFile: {ctx.file_path}\n```{ctx.language}\n{ctx.content}\n```\n"
136
+
137
  ai_solution = self.ai_provider.generate_solution(context, issue['body'])
138
+
139
+ # Create comprehensive resolution document
140
+ full_resolution = self._create_resolution_document(
141
+ issue_number, severity, resolution, ai_solution
142
+ )
143
+
144
+ # Save resolution
145
+ resolution_path = Path(RESOLUTIONS_DIRECTORY) / f"resolution_{issue_number}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
146
+ resolution_path.write_text(full_resolution)
147
+
148
+ # Prompt for manual intervention
149
+ print("\nAI-generated solution and original resolution have been saved.")
150
+ print(f"Please review and apply changes in: {self.temp_dir}")
151
+ input("Press Enter when changes are ready to commit...")
152
+
153
+ # Commit and push changes
154
+ if not self.git.commit(f"Fix #{issue_number}: {issue['title']}\n\n{resolution}"):
155
+ raise Exception("Failed to commit changes")
156
+
157
+ if not self.git.push("origin", branch_name):
158
+ raise Exception("Failed to push changes")
159
+
160
+ # Create pull request
161
+ pr = self.create_pull_request(
162
+ owner=owner,
163
+ repo=repo,
164
+ title=f"Fix #{issue_number}: {issue['title']}",
165
+ body=full_resolution,
166
+ head=branch_name
167
+ )
168
+
169
+ return f"Resolution implemented and PR created: {pr['html_url']}"
170
 
171
+ except Exception as e:
172
+ error_msg = f"Error resolving issue #{issue_number} in repository {owner}/{repo}: {str(e)}"
173
+ self.logger.error(error_msg)
174
+ return error_msg
175
+
176
+ finally:
177
+ # Cleanup
178
+ if self.temp_dir and os.path.exists(self.temp_dir):
179
+ shutil.rmtree(self.temp_dir)
180
+
181
+ def _create_resolution_document(self, issue_number: int, severity: IssueSeverity,
182
+ resolution: str, ai_solution: str) -> str:
183
+ """Create a comprehensive resolution document"""
184
+ return f"""# Resolution for Issue #{issue_number}
185
+
186
+ ## Severity: {severity.name}
187
+
188
+ ## Original Resolution
189
+ {resolution}
190
+
191
+ ## AI-Generated Solution
192
+ {ai_solution}
193
 
194
+ ## Implementation Details
195
+ - Implementation Date: {datetime.now(timezone.utc).isoformat()}
196
+ - Severity Level: {severity.name}
197
+ - Resolution Type: {'AI-ASSISTED' if ai_solution else 'MANUAL'}
 
198
 
199
+ ## Testing Notes
200
+ - [ ] Code changes have been tested locally
201
+ - [ ] All tests pass
202
+ - [ ] No new issues introduced
203
+
204
+ ## Review Checklist
205
+ - [ ] Code follows project style guidelines
206
+ - [ ] Documentation has been updated
207
+ - [ ] Changes are properly tested
208
+ - [ ] No security vulnerabilities introduced
209
+
210
+ ## Additional Notes
211
+ Please review the changes carefully before merging.
212
  """
213
 
214
  script_file = f"{RESOLUTIONS_DIRECTORY}/resolve_issue_{issue_number}.sh"
 
227
 
228
  def create_gradio_interface():
229
  with gr.Blocks() as demo:
230
+ gr.Markdown("# Enhanced GitHub Issue Resolver")
231
+ gr.Markdown("AI-assisted issue resolution with integrated git operations")
232
 
233
+ with gr.Tab("Configuration"):
234
+ with gr.Row():
235
+ token_input = gr.Textbox(label="GitHub Token", placeholder="Enter your GitHub token")
236
+ repo_url_input = gr.Textbox(label="Repository URL", placeholder="Enter the repository URL (owner/repo)")
237
+
238
+ with gr.Row():
239
+ ai_api_base = gr.Textbox(label="AI API Base URL", placeholder="Enter your AI API base URL")
240
+ ai_api_key = gr.Textbox(label="AI API Key", placeholder="Enter your AI API key")
241
 
242
+ with gr.Tab("Issue Resolution"):
243
+ with gr.Row():
244
+ issue_number_input = gr.Number(label="Issue Number", info="Enter the issue number")
245
+ severity_indicator = gr.Label(label="Issue Severity")
246
 
 
 
247
  resolution_input = gr.Textbox(label="Resolution", placeholder="Describe the resolution for the issue", lines=4)
248
+ forked_repo_input = gr.Textbox(label="Forked Repository URL", placeholder="Enter the forked repository URL")
249
 
250
+ with gr.Row():
251
+ analyze_button = gr.Button("Analyze Issue")
252
+ resolve_button = gr.Button("Resolve Issue")
253
 
254
+ result_output = gr.Textbox(label="Result", interactive=False)
 
255
 
256
+ def on_analyze(token, repo_url, issue_number):
257
+ try:
258
+ parts = repo_url.split('/')
259
+ owner, repo = parts[-2], parts[-1]
260
+
261
+ # Initialize bot
262
+ bot.initialize_api(token)
263
+
264
+ # Get issue details
265
+ issue_url = f"{bot.github_api.base_url}/repos/{owner}/{repo}/issues/{issue_number}"
266
+ response = requests.get(issue_url, headers=bot.github_api.headers)
267
+ response.raise_for_status()
268
+ issue = response.json()
269
+
270
+ # Determine severity
271
+ severity = bot._determine_severity(issue)
272
+
273
+ return f"Issue Severity: {severity.name}"
274
+ except Exception as e:
275
+ return f"Error: {str(e)}"
276
+
277
+ def on_resolve(token, repo_url, ai_api_base, ai_api_key, issue_number, resolution, forked_repo):
278
  try:
279
  parts = repo_url.split('/')
280
  owner, repo = parts[-2], parts[-1]
 
283
  ai_provider = CustomAIProvider(ai_api_base, ai_api_key)
284
  bot.initialize_api(token, ai_provider)
285
 
 
 
286
  result = bot.resolve_issue(token, owner, repo, int(issue_number), resolution, forked_repo)
287
  return result
288
  except Exception as e:
289
  return f"Error: {str(e)}"
290
 
291
+ analyze_button.click(
292
+ on_analyze,
293
+ inputs=[token_input, repo_url_input, issue_number_input],
294
+ outputs=[severity_indicator]
295
+ )
296
+
297
+ resolve_button.click(
298
+ on_resolve,
299
  inputs=[token_input, repo_url_input, ai_api_base, ai_api_key,
300
  issue_number_input, resolution_input, forked_repo_input],
301
+ outputs=[result_output]
302
  )
303
 
304
+ return demo