gh / app.py
adowu's picture
Update app.py
20dc0d7 verified
raw
history blame
22.5 kB
import gradio as gr
from github import Github, GithubException
import os
import requests
import re
from collections import Counter
# Load token. Best practice is to use environment variables.
GITHUB_TOKEN = os.getenv('GITHUB_TOKEN')
if not GITHUB_TOKEN: # Good practice to have this check.
raise ValueError("GITHUB_TOKEN environment variable not set!")
g = Github(GITHUB_TOKEN)
# Project templates (well-structured).
PROJECT_TEMPLATES = {
"flask": {
"files": {
"app.py": """from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello, Flask!"
if __name__ == '__main__':
app.run(debug=True)""",
"requirements.txt": "Flask",
".gitignore": "__pycache__/\n*.pyc\nvenv/\ninstance/"
}
},
"react": {
"files": {
"package.json": """{
"name": "react-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.1",
"vite": "^5.0.8"
}}""",
"vite.config.js": """import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
# https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})""",
"index.html": """<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>""",
"src/main.jsx": """import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)""",
"src/App.jsx": """import React from 'react'
import './App.css'
function App() {
return (
<>
<h1>Hello from React!</h1>
</>
)
}
export default App""",
"src/index.css": """body {
margin: 0;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: monospace;
}""",
".gitignore": "node_modules/\ndist/"
}
}}
def get_file_content(owner, repo_name, path, branch="main"):
"""Fetches file content from a GitHub repository."""
url = f"https://raw.githubusercontent.com/{owner}/{repo_name}/{branch}/{path}"
try:
response = requests.get(url)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
return response.text
except requests.exceptions.RequestException as e:
return f"Error fetching file: {e}"
def extract_repo_info(url):
"""Extracts owner and repo name from a GitHub URL."""
match = re.search(r"github\.com/([^/]+)/([^/]+)", url)
return match.group(1), match.group(2) if match else (None, None)
def analyze_file_content(content, file_path):
"""Analyzes file content and returns statistics."""
lines = content.splitlines()
word_count = sum(len(line.split()) for line in lines)
line_count = len(lines)
file_extension = file_path.split('.')[-1].lower() if '.' in file_path else "unknown"
return {
"line_count": line_count,
"word_count": word_count,
"file_extension": file_extension,
}
def github_tool(
action: str,
repo_name: str = None,
branch: str = "main",
path: str = None,
content: str = None,
message: str = None,
owner: str = None,
vcs_url: str = None,
title: str = None,
body: str = None,
base: str = None,
head: str = None,
issue_number: int = None,
labels: str = None,
tag: str = None,
name: str = None,
file_url: str = None,
repo_url: str = None,
template_name: str = None,
):
"""Manages GitHub repositories."""
user = g.get_user()
try: # Single try block for ALL actions (Solution 1)
if action == "import_repository":
if not all([owner, repo_name, vcs_url]):
raise ValueError("Missing parameters: owner, repo_name, vcs_url")
try:
user.get_repo(repo_name) # Check if repo exists (simplified)
return "Repository already exists."
except GithubException:
pass # Repo doesn't exist, proceed with import
headers = {
'Authorization': f'token {GITHUB_TOKEN}',
'Accept': 'application/vnd.github.v3+json',
}
import_url = f'https://api.github.com/repos/{owner}/{repo_name}/import'
response = requests.put(import_url, json={'vcs_url': vcs_url, 'vcs': 'git'}, headers=headers)
response.raise_for_status() # Handle potential HTTP errors.
return "Repository import started."
elif action == "create_repository":
if not repo_name:
raise ValueError("Missing parameter: repo_name")
repo = user.create_repo(name=repo_name)
return f"Repository **{repo_name}** created! [Open repository]({repo.html_url})"
elif action == "create_project_from_template":
if not all([repo_name, template_name]):
raise ValueError("Missing parameters: repo_name, template_name")
if template_name not in PROJECT_TEMPLATES:
raise ValueError(
f"Unknown template: {template_name}. Available: {', '.join(PROJECT_TEMPLATES.keys())}"
)
repo = user.create_repo(name=repo_name)
template = PROJECT_TEMPLATES[template_name]
for file_path, file_content in template["files"].items():
repo.create_file(
file_path,
f"Create {file_path} from template {template_name}",
file_content,
branch="main",
)
return f"Repository **{repo_name}** created from template **{template_name}**! [Open repository]({repo.html_url})"
elif action == "create_file":
if not all([repo_name, path, content, message]):
raise ValueError("Missing parameters: repo_name, path, content, message")
repo = user.get_repo(repo_name)
repo.create_file(path, message, content, branch=branch)
return f"File **`{path}`** created in repository **`{repo_name}`** on branch **`{branch}`**."
elif action == "get_file":
if not all([repo_name, path]):
raise ValueError("Missing parameters: repo_name, path")
repo = user.get_repo(repo_name)
file_content = repo.get_contents(path, ref=branch)
return (
f"File content of **`{path}`** from **`{repo_name}`**:\n\n`\n{file_content.decoded_content.decode()}\n`"
)
elif action == "get_file_content_by_url":
if not file_url:
raise ValueError("Missing parameter: file_url")
file_content = get_file_content(None, None, None, None) # Dummy call to avoid UnboundLocalError. get_file_content changed!
return f"File content from URL **`{file_url}`**:\n\n`\n{file_content}\n`"
elif action == "delete_file":
if not all([repo_name, path]):
raise ValueError("Missing parameters: repo_name, path")
repo = user.get_repo(repo_name)
file_contents = repo.get_contents(path, ref=branch)
repo.delete_file(path, "Delete file", file_contents.sha, branch=branch)
return f"File **`{path}`** deleted from repository **`{repo_name}`** on branch **`{branch}`**."
elif action == "update_file":
if not all([repo_name, path, content, message]):
raise ValueError("Missing parameters: repo_name, path, content, message")
repo = user.get_repo(repo_name)
file_contents = repo.get_contents(path, ref=branch)
repo.update_file(path, message, content, file_contents.sha, branch=branch)
return f"File **`{path}`** updated in repository **`{repo_name}`** on branch **`{branch}`**."
elif action == "list_branches":
if not repo_name:
raise ValueError("Missing parameter: repo_name")
repo = user.get_repo(repo_name)
branches = repo.get_branches()
branch_list = "\n".join([f"- `{branch.name}`" for branch in branches])
return f"Branches in repository **`{repo_name}`**:\n{branch_list}"
elif action == "create_branch":
if not all([repo_name, base, head]):
raise ValueError("Missing parameters: repo_name, base, head")
repo = user.get_repo(repo_name)
source_branch = repo.get_branch(base)
repo.create_git_ref(ref=f"refs/heads/{head}", sha=source_branch.commit.sha)
return f"Branch **`{head}`** created from **`{base}`** in repository **`{repo_name}`**."
elif action == "delete_branch":
if not all([repo_name, branch]):
raise ValueError("Missing parameters: repo_name, branch")
repo = user.get_repo(repo_name)
repo.get_git_ref(f"heads/{branch}").delete()
return f"Branch **`{branch}`** deleted from repository **`{repo_name}`**."
elif action == "create_pull_request":
if not all([repo_name, title, body, base, head]):
raise ValueError("Missing parameters: repo_name, title, body, base, head")
repo = user.get_repo(repo_name)
pr = repo.create_pull(title=title, body=body, base=base, head=head)
return f"Pull request created! [Open Pull Request]({pr.html_url})"
elif action == "list_open_pull_requests":
if not repo_name:
raise ValueError("Missing parameter: repo_name")
repo = user.get_repo(repo_name)
open_prs = repo.get_pulls(state='open')
if not open_prs:
return f"No open pull requests in repository **`{repo_name}`**."
prs_list = "\n".join([f"- [{pr.title}]({pr.html_url})" for pr in open_prs])
return f"Open pull requests in repository **`{repo_name}`**:\n{prs_list}"
elif action == "create_issue":
if not all([repo_name, title, body]):
raise ValueError("Missing parameters: repo_name, title, body")
repo = user.get_repo(repo_name)
issue = repo.create_issue(title=title, body=body)
return f"Issue created! [Open Issue]({issue.html_url})"
elif action == "list_issues":
if not repo_name:
raise ValueError("Missing parameter: repo_name")
repo = user.get_repo(repo_name)
issues = repo.get_issues(state='open')
if not issues:
return f"No open issues in repository **`{repo_name}`**."
issues_list = "\n".join([f"- [{issue.title}]({issue.html_url})" for issue in issues])
return f"Open issues in repository **`{repo_name}`**:\n{issues_list}"
elif action == "add_label_to_issue":
if not all([repo_name, issue_number, labels]):
raise ValueError("Missing parameters: repo_name, issue_number, labels")
repo = user.get_repo(repo_name)
issue = repo.get_issue(number=int(issue_number))
for label in labels.split(","):
issue.add_to_labels(label.strip())
return (
f"Labels **`{labels}`** added to issue **#{issue_number}** in repository **`{repo_name}`**."
)
elif action == "close_issue":
if not all([repo_name, issue_number]):
raise ValueError("Missing parameters: repo_name, issue_number")
repo = user.get_repo(repo_name)
issue = repo.get_issue(number=int(issue_number))
issue.edit(state='closed')
return f"Issue **#{issue_number}** closed in repository **`{repo_name}`**."
elif action == "add_comment_to_issue":
if not all([repo_name, issue_number, message]):
raise ValueError("Missing parameters: repo_name, issue_number, message")
repo = user.get_repo(repo_name)
issue = repo.get_issue(number=int(issue_number))
issue.create_comment(body=message)
return f"Comment added to issue **#{issue_number}** in repository **`{repo_name}`**."
elif action == "create_release":
if not all([repo_name, tag, name, message]):
raise ValueError("Missing parameters: repo_name, tag, name, message")
repo = user.get_repo(repo_name)
release = repo.create_git_release(tag=tag, name=name, message=message)
return (
f"Release **`{name}`** created in repository **`{repo_name}`**! [Open Release]({release.html_url})"
)
elif action == "list_releases":
if not repo_name:
raise ValueError("Missing parameter: repo_name")
repo = user.get_repo(repo_name)
releases = repo.get_releases()
if not releases:
return f"No releases in repository **`{repo_name}`**."
releases_list = "\n".join([f"- [{release.tag_name}]({release.html_url})" for release in releases])
return f"Releases in repository **`{repo_name}`**:\n{releases_list}"
elif action == "fork_repository":
if not repo_name:
raise ValueError("Missing parameter: repo_name")
repo = g.get_repo(repo_name)
fork = user.create_fork(repo)
return f"Repository **`{repo_name}`** forked! [Open fork]({fork.html_url})"
elif action == "list_forks":
if not repo_name:
raise ValueError("Missing parameter: repo_name")
repo = g.get_repo(repo_name)
forks = repo.get_forks()
if not forks:
return f"No forks of repository **`{repo_name}`**."
forks_list = "\n".join([f"- [{fork.full_name}]({fork.html_url})" for fork in forks])
return f"Forks of repository **`{repo_name}`**:\n{forks_list}"
elif action == "list_files":
if not all([owner, repo_name]):
raise ValueError("Missing parameters: owner, repo_name")
repo = g.get_repo(f"{owner}/{repo_name}")
contents = repo.get_contents("" if not path else path) # Simplified path handling
if not contents:
return f"No files in path **`{path}`** of repository **`{repo_name}`**."
files_list = "\n".join([f"- [{content.name}]({content.download_url})" for content in contents])
return f"Files in path **`{path}`** of repository **`{repo_name}`**:\n{files_list}"
elif action == "get_repository_info":
if not all([owner, repo_name]):
raise ValueError("Missing parameters: owner, repo_name")
repo = g.get_repo(f"{owner}/{repo_name}")
info = {
"Name": repo.name,
"Description": repo.description,
"URL": repo.html_url,
"Owner": repo.owner.login,
"Default branch": repo.default_branch,
"Language": repo.language,
"Stars": repo.stargazers_count,
"Forks": repo.forks_count,
"Created at": str(repo.created_at),
"Last updated": str(repo.updated_at),
}
info_md = "\n".join([f"- **{key}**: {value}" for key, value in info.items()])
return f"Repository info for **`{repo_name}`**:\n{info_md}"
elif action == "get_file_content":
if not all([owner, repo_name, path]):
raise ValueError("Missing parameters: owner, repo_name, path")
content_text = get_file_content(owner, repo_name, path, branch)
return (
f"File content of **`{path}`** from repository **`{repo_name}`**:\n\n`\n{content_text}\n`"
)
elif action == "analyze_repository_by_url":
if not repo_url:
raise ValueError("Missing parameter: repo_url")
owner, repo_name = extract_repo_info(repo_url)
if not owner or not repo_name:
raise ValueError("Invalid repository URL")
repo = g.get_repo(f"{owner}/{repo_name}")
contents = repo.get_contents("")
file_analyses = []
for content in contents:
if content.type == "file":
file_content = content.decoded_content.decode()
analysis = analyze_file_content(file_content, content.path)
file_analyses.append({
"name": content.name,
"path": content.path,
"analysis": analysis,
})
analysis_md = "Repository file analysis:\n" + "\n".join([
f"- **{f['path']}**:\n"
f" - Lines: {f['analysis']['line_count']}\n"
f" - Words: {f['analysis']['word_count']}\n"
f" - Extension: {f['analysis']['file_extension']}"
for f in file_analyses
])
return analysis_md
elif action == "analyze_repository_content":
if not all([owner, repo_name]):
raise ValueError("Missing parameters: owner, repo_name")
repo = g.get_repo(f"{owner}/{repo_name}")
contents = repo.get_contents("")
file_analyses = []
for content in contents:
if content.type == "file":
file_content = get_file_content(owner, repo_name, content.path, branch) # Use helper
analysis = analyze_file_content(file_content, content.path)
file_analyses.append({
"name": content.name,
"path": content.path,
"analysis": analysis,
})
analysis_md = "Repository content analysis:\n" + "\n".join([
f"- **{f['path']}**:\n"
f" - Lines: {f['analysis']['line_count']}\n"
f" - Words: {f['analysis']['word_count']}\n"
f" - Extension: {f['analysis']['file_extension']}"
for f in file_analyses
])
return analysis_md
else: # Correct else for handling unknown actions
raise ValueError(f"Unknown action: {action}")
except GithubException as e:
return f"**GitHub Error:** {e}"
except ValueError as e:
return f"**Error:** {e}" # This is line 434 (now corrected)
except requests.exceptions.RequestException as e:
return f"**Connection Error:** {e}"
except Exception as e:
return f"**Unexpected Error:** {e}" # Catch all.
with gr.Blocks() as demo:
gr.Markdown("# GitHub Tool Plugingit (Simplified Interface)")
with gr.Column():
action = gr.Dropdown(
choices=[
"import_repository",
"create_repository",
"create_project_from_template",
"create_file",
"get_file",
"get_file_content_by_url",
"delete_file",
"update_file",
"list_branches",
"create_branch",
"delete_branch",
"create_pull_request",
"list_open_pull_requests",
"create_issue",
"list_issues",
"add_label_to_issue",
"close_issue",
"add_comment_to_issue",
"create_release",
"list_releases",
"fork_repository",
"list_forks",
"list_files",
"get_repository_info",
"get_file_content",
"analyze_repository_by_url",
"analyze_repository_content",
],
label="Select Action",
)
repo_name = gr.Textbox(label="Repository Name")
template_name = gr.Dropdown(
choices=[""] + list(PROJECT_TEMPLATES.keys()), # Add "" as a choice
label="Project Template",
value="", # Set default value to "" (no selection)
allow_custom_value=True, # Use allow_custom_value instead of allow_none
)
branch = gr.Textbox(label="Branch", value="main")
path = gr.Textbox(label="File Path")
content = gr.Code(label="File Content", lines=5, language='python') # Larger code box
message = gr.Textbox(label="Message/Comment")
owner = gr.Textbox(label="Owner")
vcs_url = gr.Textbox(label="VCS URL")
title = gr.Textbox(label="Title")
body = gr.Textbox(label="Body")
base = gr.Textbox(label="Base Branch")
head = gr.Textbox(label="Head Branch/New Branch")
issue_number = gr.Number(label="Issue Number", precision=0)
labels = gr.Textbox(label="Labels (comma-separated)")
tag = gr.Textbox(label="Tag")
release_name = gr.Textbox(label="Release Name")
file_url = gr.Textbox(label="File URL")
repo_url = gr.Textbox(label="Repository URL")
run_button = gr.Button("Execute")
output = gr.Markdown(label="Result") # Use Markdown for richer output
run_button.click(
github_tool,
inputs=[
action,
repo_name,
branch,
path,
content,
message,
owner,
vcs_url,
title,
body,
base,
head,
issue_number,
labels,
tag,
release_name,
file_url,
repo_url,
template_name,
],
outputs=output,
api_name="github_tool"
)
demo.launch()