GitBot / app.py
acecalisto3's picture
Update app.py
1a8f43c verified
raw
history blame
11.7 kB
import gradio as gr
import langchain
import huggingface_hub
import dotenv
import PyYaml
from typing import Optional, Union, List, Dict, Any, Tuple
import subprocess
from pathlib import Path
import json
import tempfile
from datetime import datetime, timezone
import re
# Add these utility classes based on git-bob's architecture
class TerminalCommand:
@staticmethod
def execute(command: Union[str, List[str]], cwd: Optional[str] = None) -> Tuple[str, str, int]:
"""
Execute a terminal command and return stdout, stderr, and return code
"""
if isinstance(command, str):
command = command.split()
try:
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd,
text=True
)
stdout, stderr = process.communicate()
return stdout.strip(), stderr.strip(), process.returncode
except Exception as e:
return "", str(e), 1
class GitUtilities:
def __init__(self, repo_path: str):
self.repo_path = Path(repo_path)
def clone(self, url: str, branch: str = "main") -> bool:
"""Clone a repository"""
_, stderr, code = TerminalCommand.execute(
f"git clone -b {branch} {url} {self.repo_path}"
)
return code == 0
def commit(self, message: str) -> bool:
"""Create a commit with the given message"""
_, _, code = TerminalCommand.execute(
["git", "commit", "-m", message],
str(self.repo_path)
)
return code == 0
def push(self, remote: str = "origin", branch: str = "main") -> bool:
"""Push changes to remote"""
_, _, code = TerminalCommand.execute(
["git", "push", remote, branch],
str(self.repo_path)
)
return code == 0
def create_branch(self, branch_name: str) -> bool:
"""Create and checkout a new branch"""
_, _, code = TerminalCommand.execute(
["git", "checkout", "-b", branch_name],
str(self.repo_path)
)
return code == 0
# Enhance the GitHubBot class with new features
class GitHubBot:
def __init__(self, logger: logging.Logger):
self.github_api = None
self.logger = logger
self.ai_provider = None
self.file_types = {
'.py': {'language': 'Python', 'parser': 'python'},
'.js': {'language': 'JavaScript', 'parser': 'javascript'},
'.ts': {'language': 'TypeScript', 'parser': 'typescript'},
'.java': {'language': 'Java', 'parser': 'java'},
'.cpp': {'language': 'C++', 'parser': 'cpp'},
'.go': {'language': 'Go', 'parser': 'go'},
'.rs': {'language': 'Rust', 'parser': 'rust'}
}
self.git = None
self.temp_dir = None
def initialize_api(self, token: str, ai_provider: AIProvider = None):
self.github_api = GitHubAPI(token, self.logger)
self.ai_provider = ai_provider
self.temp_dir = tempfile.mkdtemp()
self.git = GitUtilities(self.temp_dir)
def create_pull_request(self, owner: str, repo: str, title: str, body: str, head: str, base: str = "main") -> Dict:
"""Create a pull request"""
url = f"{self.github_api.base_url}/repos/{owner}/{repo}/pulls"
data = {
"title": title,
"body": body,
"head": head,
"base": base
}
response = requests.post(url, headers=self.github_api.headers, json=data)
response.raise_for_status()
return response.json()
def resolve_issue(self, token: str, owner: str, repo: str, issue_number: int, resolution: str, forked_repo: str) -> str:
try:
if not self.ai_provider:
raise ValueError("AI provider not initialized. Please initialize with an AI provider.")
# Create a unique branch name for this fix
branch_name = f"fix/issue-{issue_number}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
# Clone repository and create new branch
if not self.git.clone(forked_repo):
raise Exception("Failed to clone repository")
if not self.git.create_branch(branch_name):
raise Exception("Failed to create branch")
# Get repository content and analyze
code_contexts = self._get_main_branch_content(owner, repo)
# Get issue details
issue_url = f"{self.github_api.base_url}/repos/{owner}/{repo}/issues/{issue_number}"
response = requests.get(issue_url, headers=self.github_api.headers)
response.raise_for_status()
issue = response.json()
# Determine severity
severity = self._determine_severity(issue)
# Generate AI solution
context = f"Repository: {owner}/{repo}\nIssue #{issue_number}: {issue['title']}\n\nCode contexts:\n"
for ctx in code_contexts[:3]:
context += f"\nFile: {ctx.file_path}\n```{ctx.language}\n{ctx.content}\n```\n"
ai_solution = self.ai_provider.generate_solution(context, issue['body'])
# Create comprehensive resolution document
full_resolution = self._create_resolution_document(
issue_number, severity, resolution, ai_solution
)
# Save resolution
resolution_path = Path(RESOLUTIONS_DIRECTORY) / f"resolution_{issue_number}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
resolution_path.write_text(full_resolution)
# Prompt for manual intervention
print("\nAI-generated solution and original resolution have been saved.")
print(f"Please review and apply changes in: {self.temp_dir}")
input("Press Enter when changes are ready to commit...")
# Commit and push changes
if not self.git.commit(f"Fix #{issue_number}: {issue['title']}\n\n{resolution}"):
raise Exception("Failed to commit changes")
if not self.git.push("origin", branch_name):
raise Exception("Failed to push changes")
# Create pull request
pr = self.create_pull_request(
owner=owner,
repo=repo,
title=f"Fix #{issue_number}: {issue['title']}",
body=full_resolution,
head=branch_name
)
return f"Resolution implemented and PR created: {pr['html_url']}"
except Exception as e:
error_msg = f"Error resolving issue #{issue_number} in repository {owner}/{repo}: {str(e)}"
self.logger.error(error_msg)
return error_msg
finally:
# Cleanup
if self.temp_dir and os.path.exists(self.temp_dir):
shutil.rmtree(self.temp_dir)
def _create_resolution_document(self, issue_number: int, severity: IssueSeverity,
resolution: str, ai_solution: str) -> str:
"""Create a comprehensive resolution document"""
return f"""# Resolution for Issue #{issue_number}
## Severity: {severity.name}
## Original Resolution
{resolution}
## AI-Generated Solution
{ai_solution}
## Implementation Details
- Implementation Date: {datetime.now(timezone.utc).isoformat()}
- Severity Level: {severity.name}
- Resolution Type: {'AI-ASSISTED' if ai_solution else 'MANUAL'}
## Testing Notes
- [ ] Code changes have been tested locally
- [ ] All tests pass
- [ ] No new issues introduced
## Review Checklist
- [ ] Code follows project style guidelines
- [ ] Documentation has been updated
- [ ] Changes are properly tested
- [ ] No security vulnerabilities introduced
## Additional Notes
Please review the changes carefully before merging.
"""
# The following lines should not be part of the _create_resolution_document method
script_file = f"{RESOLUTIONS_DIRECTORY}/resolve_issue_{issue_number}.sh"
with open(script_file, "w") as f:
f.write(bash_script)
# Make the script executable
os.chmod(script_file, 0o755)
return f"Bash script generated: {script_file}"
except Exception as e:
error_msg = f"Error resolving issue #{issue_number} in repository {owner}/{repo}: {str(e)}"
self.logger.error(error_msg)
return error_msg
def create_gradio_interface():
with gr.Blocks() as demo:
gr.Markdown("# Enhanced GitHub Issue Resolver")
gr.Markdown("AI-assisted issue resolution with integrated git operations")
with gr.Tab("Configuration"):
with gr.Row():
token_input = gr.Textbox(label="GitHub Token", placeholder="Enter your GitHub token")
repo_url_input = gr.Textbox(label="Repository URL", placeholder="Enter the repository URL (owner/repo)")
with gr.Row():
ai_api_base = gr.Textbox(label="AI API Base URL", placeholder="Enter your AI API base URL")
ai_api_key = gr.Textbox(label="AI API Key", placeholder="Enter your AI API key")
with gr.Tab("Issue Resolution"):
with gr.Row():
issue_number_input = gr.Number(label="Issue Number", info="Enter the issue number")
severity_indicator = gr.Label(label="Issue Severity")
resolution_input = gr.Textbox(label="Resolution", placeholder="Describe the resolution for the issue", lines=4)
forked_repo_input = gr.Textbox(label="Forked Repository URL", placeholder="Enter the forked repository URL")
with gr.Row():
analyze_button = gr.Button("Analyze Issue")
resolve_button = gr.Button("Resolve Issue")
result_output = gr.Textbox(label="Result", interactive=False)
def on_analyze(token, repo_url, issue_number):
try:
parts = repo_url.split('/')
owner, repo = parts[-2], parts[-1]
# Initialize bot
bot.initialize_api(token)
# Get issue details
issue_url = f"{bot.github_api.base_url}/repos/{owner}/{repo}/issues/{issue_number}"
response = requests.get(issue_url, headers=bot.github_api.headers)
response.raise_for_status()
issue = response.json()
# Determine severity
severity = bot._determine_severity(issue)
return f"Issue Severity: {severity.name}"
except Exception as e:
return f"Error: {str(e)}"
def on_resolve(token, repo_url, ai_api_base, ai_api_key, issue_number, resolution, forked_repo):
try:
parts = repo_url.split('/')
owner, repo = parts[-2], parts[-1]
# Initialize bot with AI provider
ai_provider = CustomAIProvider(ai_api_base, ai_api_key)
bot.initialize_api(token, ai_provider)
result = bot.resolve_issue(token, owner, repo, int(issue_number), resolution, forked_repo)
return result
except Exception as e:
return f"Error: {str(e)}"
analyze_button.click(
on_analyze,
inputs=[token_input, repo_url_input, issue_number_input],
outputs=[severity_indicator]
)
resolve_button.click(
on_resolve,
inputs=[token_input, repo_url_input, ai_api_base, ai_api_key,
issue_number_input, resolution_input, forked_repo_input],
outputs=[result_output]
)
return demo