Gitu / app.py
nihalaninihal's picture
Update app.py
b4986a9 verified
# app.py - Improved version
import os
import gradio as gr
from github_ai_agent import GitHubAIAgent
import markdown
from typing import Tuple, Dict, Any, List
import google.generativeai as genai
def create_web_interface(agent: GitHubAIAgent) -> gr.Blocks:
"""Create enhanced Gradio web interface for the GitHub AI Agent with improved UI"""
# Custom CSS for better styling
custom_css = """
.gradio-container {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* Repository Card */
.repository-card {
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
padding: 20px;
margin-bottom: 20px;
background-color: #f9f9f9;
transition: transform 0.2s, box-shadow 0.2s;
}
.repository-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 10px rgba(0,0,0,0.1);
}
/* Authentication Badge */
.auth-badge {
display: inline-block;
padding: 5px 10px;
border-radius: 15px;
font-size: 12px;
font-weight: bold;
margin-bottom: 10px;
}
.auth-authenticated {
background-color: #d4edda;
color: #155724;
}
.auth-unauthenticated {
background-color: #f8d7da;
color: #721c24;
}
/* Metric Cards */
.metric-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 15px;
}
.metric-card {
flex: 1 1 100px;
padding: 15px;
border-radius: 8px;
background-color: #ffffff;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
text-align: center;
}
.metric-value {
font-size: 24px;
font-weight: bold;
margin: 10px 0;
color: #2980b9;
}
.metric-label {
font-size: 12px;
color: #7f8c8d;
}
/* Progress Bar */
.progress-container {
width: 100%;
background-color: #e0e0e0;
border-radius: 5px;
margin: 10px 0;
}
.progress-bar {
height: 5px;
border-radius: 5px;
background-color: #3498db;
}
/* Theme Toggle Switch */
.theme-switch-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.theme-switch {
position: relative;
display: inline-block;
width: 60px;
height: 30px;
margin-left: 10px;
}
.theme-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 22px;
width: 22px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #2196F3;
}
input:checked + .slider:before {
transform: translateX(30px);
}
/* Dark Theme Styles */
.dark-theme {
background-color: #2c3e50;
color: #ecf0f1;
}
.dark-theme .metric-card {
background-color: #34495e;
color: #ecf0f1;
}
.dark-theme .metric-value {
color: #3498db;
}
.dark-theme .repository-card {
background-color: #34495e;
color: #ecf0f1;
}
/* Loading Animation */
@keyframes pulse {
0% { opacity: 0.6; }
50% { opacity: 1; }
100% { opacity: 0.6; }
}
.loading {
animation: pulse 1.5s infinite;
border-radius: 8px;
background-color: #f0f0f0;
padding: 20px;
text-align: center;
}
/* Tooltip */
.tooltip {
position: relative;
display: inline-block;
cursor: help;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
/* Improved tabs */
.tab-nav {
border-bottom: 2px solid #e0e0e0;
margin-bottom: 20px;
}
.tab-button {
padding: 10px 20px;
background: transparent;
border: none;
cursor: pointer;
font-weight: bold;
color: #7f8c8d;
position: relative;
}
.tab-button.active {
color: #3498db;
}
.tab-button.active:after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 2px;
background-color: #3498db;
}
"""
def load_repository(repo_url: str, gemini_key: str, use_token: bool = False, github_token: str = "") -> Tuple[str, str, float]:
"""Load a repository (Gradio wrapper) with progress tracking"""
try:
if use_token and not github_token:
return "Error: GitHub token is required for private repositories.", "", 0.0
# Set API keys
actual_github_token = github_token if use_token else ""
agent.set_api_keys(gemini_key, actual_github_token)
if not agent.config.gemini_api_key:
return "Error: Gemini API key is required.", "", 0.0
# Progress indicators (for UI feedback)
progress_value = 0.1
progress_message = "Initializing repository..."
yield progress_message, "", progress_value
# Load repository
progress_value = 0.2
progress_message = f"Loading repository: {repo_url}"
yield progress_message, "", progress_value
result = agent.load_repository(repo_url)
if not result['success']:
return f"Error: {result['message']}", "", 0.0
# Indicate progress
progress_value = 0.6
progress_message = "Repository loaded. Building knowledge base..."
yield progress_message, "", progress_value
# Get repository insights
insights = agent.get_repository_insights()
# Create HTML report
analysis_content = insights['analysis']['analysis'].replace('\n', '<br>') if insights['success'] else "No analysis available."
# Calculate badge text and class
badge_text = "Private Repository" if use_token else "Public Repository"
badge_class = "auth-authenticated" if use_token else "auth-unauthenticated"
# Format date properly
created_at = result['repo_data'].get('created_at', 'Unknown')
if hasattr(created_at, 'strftime'):
created_at = created_at.strftime('%Y-%m-%d')
updated_at = result['repo_data'].get('updated_at', 'Unknown')
if hasattr(updated_at, 'strftime'):
updated_at = updated_at.strftime('%Y-%m-%d')
# Build HTML with improved styling
html_report = f"""
<div class="repository-card">
<div class="auth-badge {badge_class}">{badge_text}</div>
<h2>Repository: {result['repo_data']['full_name']}</h2>
<p><strong>Description:</strong> {result['repo_data']['description'] or "No description available"}</p>
<div class="metric-container">
<div class="metric-card">
<div class="metric-label">Stars</div>
<div class="metric-value">{result['repo_data']['stars']}</div>
</div>
<div class="metric-card">
<div class="metric-label">Forks</div>
<div class="metric-value">{result['repo_data']['forks']}</div>
</div>
<div class="metric-card">
<div class="metric-label">Language</div>
<div class="metric-value">{result['repo_data']['language'] or "N/A"}</div>
</div>
<div class="metric-card">
<div class="metric-label">Files</div>
<div class="metric-value">{result['file_count']}</div>
</div>
<div class="metric-card">
<div class="metric-label">Contributors</div>
<div class="metric-value">{result['contributor_count']}</div>
</div>
</div>
<p><strong>Created:</strong> {created_at} | <strong>Last Updated:</strong> {updated_at}</p>
<p><strong>License:</strong> {result['repo_data'].get('license', 'None')}</p>
<h3>Topics</h3>
<p>{', '.join(result['repo_data'].get('topics', ['None'])) if result['repo_data'].get('topics') else 'None'}</p>
</div>
<h3>Repository Analysis</h3>
<div class="repository-card">
{analysis_content}
</div>
"""
# Complete progress
progress_value = 1.0
progress_message = "Analysis complete!"
yield progress_message, markdown.markdown(html_report), progress_value
return progress_message, markdown.markdown(html_report), progress_value
except Exception as e:
return f"Error: {str(e)}", "", 0.0
def ask_question(question: str) -> str:
"""Ask a question about the repository (Gradio wrapper)"""
if not question:
return "Please enter a question."
if not agent.repository_loaded:
return "Please load a repository first."
try:
result = agent.answer_query(question)
if result['success']:
# Format the answer with improved styling
answer_content = result['answer'].replace('\n', '<br>')
html_response = f"""
<div class="repository-card">
<h3>Answer</h3>
<div>{answer_content}</div>
"""
if result['relevant_files']:
html_response += f"""
<h4>Relevant Files:</h4>
<ul>
"""
for file in result['relevant_files']:
html_response += f"<li><code>{file}</code></li>"
html_response += "</ul>"
html_response += "</div>"
return markdown.markdown(html_response)
else:
return f"Error: {result['message']}"
except Exception as e:
return f"Error: {str(e)}"
def analyze_code(file_path: str, code_snippet: str) -> str:
"""Analyze code (Gradio wrapper)"""
if not file_path and not code_snippet:
return "Please provide either a file path or a code snippet."
try:
result = agent.analyze_code(file_path, code_snippet)
if result['success']:
# Format the analysis with improved styling
analysis_content = result['analysis'].replace('\n', '<br>')
html_response = f"""
<div class="repository-card">
<h3>Code Analysis</h3>
<p><strong>{"File" if file_path else "Code Snippet"}:</strong> {file_path if file_path else "Provided snippet"}</p>
<div>{analysis_content}</div>
</div>
"""
return markdown.markdown(html_response)
else:
return f"Error: {result['message']}"
except Exception as e:
return f"Error: {str(e)}"
def find_collaborators(requirements: str) -> str:
"""Find potential collaborators (Gradio wrapper)"""
if not requirements:
return "Please provide requirements for collaborators."
if not agent.repository_loaded:
return "Please load a repository first."
try:
result = agent.find_collaborators(requirements)
if result['success']:
html_response = "<h3>Potential Collaborators</h3>"
for collaborator in result['collaborators']:
confidence = collaborator.get('confidence', 0)
confidence_percent = f"{confidence:.1%}" if isinstance(confidence, float) else "N/A"
# Apply color based on confidence
if isinstance(confidence, float):
if confidence >= 0.8:
confidence_color = "#27ae60" # Green for high confidence
elif confidence >= 0.5:
confidence_color = "#f39c12" # Yellow for medium confidence
else:
confidence_color = "#e74c3c" # Red for low confidence
else:
confidence_color = "#7f8c8d" # Gray for N/A
html_response += f"""
<div class="repository-card">
<h4>{collaborator['login']}</h4>
<p><strong>Confidence: </strong><span style="color: {confidence_color}; font-weight: bold;">{confidence_percent}</span></p>
<p><strong>Reasons:</strong></p>
<ul>
"""
for reason in collaborator.get('reasons', []):
html_response += f"<li>{reason}</li>"
html_response += """
</ul>
</div>
"""
return markdown.markdown(html_response)
else:
return f"Error: {result['message']}"
except Exception as e:
return f"Error: {str(e)}"
def get_insights() -> str:
"""Get repository insights (Gradio wrapper)"""
if not agent.repository_loaded:
return "Please load a repository first."
try:
result = agent.get_repository_insights()
if result['success']:
insights = result['insights']
html_response = "<h2>Repository Insights</h2>"
if 'basic_stats' in insights:
html_response += """
<div class="repository-card">
<h3>Basic Statistics</h3>
<ul>
"""
html_response += f"<li><strong>Repository:</strong> {insights['basic_stats'].get('name', 'N/A')}</li>"
html_response += f"<li><strong>Description:</strong> {insights['basic_stats'].get('description', 'N/A')}</li>"
html_response += f"<li><strong>Stars:</strong> {insights['basic_stats'].get('stars', 0)}</li>"
html_response += f"<li><strong>Forks:</strong> {insights['basic_stats'].get('forks', 0)}</li>"
html_response += f"<li><strong>Age:</strong> {insights['basic_stats'].get('age_days', 0)} days</li>"
html_response += f"<li><strong>Primary Language:</strong> {insights['basic_stats'].get('primary_language', 'N/A')}</li>"
if 'topics' in insights['basic_stats']:
html_response += f"<li><strong>Topics:</strong> {', '.join(insights['basic_stats']['topics'])}</li>"
html_response += """
</ul>
</div>
"""
if 'activity' in insights:
html_response += """
<div class="repository-card">
<h3>Activity</h3>
<div class="metric-container">
"""
# Add metrics
metrics = [
("Total Commits", insights['activity'].get('total_commits', 0)),
("Days Span", insights['activity'].get('days_span', 0)),
("Commits Per Day", insights['activity'].get('commits_per_day', 0))
]
for label, value in metrics:
html_response += f"""
<div class="metric-card">
<div class="metric-label">{label}</div>
<div class="metric-value">{value}</div>
</div>
"""
html_response += """
</div>
<ul>
"""
if 'first_commit' in insights['activity']:
first_commit = insights['activity']['first_commit']
if hasattr(first_commit, 'strftime'):
first_commit = first_commit.strftime('%Y-%m-%d')
html_response += f"<li><strong>First Commit:</strong> {first_commit}</li>"
if 'last_commit' in insights['activity']:
last_commit = insights['activity']['last_commit']
if hasattr(last_commit, 'strftime'):
last_commit = last_commit.strftime('%Y-%m-%d')
html_response += f"<li><strong>Last Commit:</strong> {last_commit}</li>"
if 'monthly_activity' in insights['activity']:
html_response += "<li><strong>Monthly Activity:</strong><ul>"
for month_data in insights['activity']['monthly_activity'][:5]:
html_response += f"<li>{month_data['month']}: {month_data['commits']} commits</li>"
html_response += "</ul></li>"
html_response += """
</ul>
</div>
"""
if 'contributors' in insights:
html_response += """
<div class="repository-card">
<h3>Contributors</h3>
<div class="metric-container">
"""
# Add metrics
metrics = [
("Total Contributors", insights['contributors'].get('total_contributors', 0)),
("Bus Factor", insights['contributors'].get('bus_factor', 0))
]
for label, value in metrics:
html_response += f"""
<div class="metric-card">
<div class="metric-label">{label}</div>
<div class="metric-value">{value}</div>
</div>
"""
html_response += """
</div>
"""
if 'top_contributors' in insights['contributors']:
html_response += "<h4>Top Contributors</h4><ul>"
for contributor in insights['contributors']['top_contributors'][:5]:
html_response += f"<li><strong>{contributor['login']}:</strong> {contributor['contributions']} contributions"
if contributor['top_files']:
html_response += f" (Top files: {', '.join(contributor['top_files'][:3])})"
html_response += "</li>"
html_response += "</ul>"
html_response += """
</div>
"""
if 'code' in insights:
html_response += """
<div class="repository-card">
<h3>Code</h3>
"""
if 'central_files' in insights['code']:
html_response += "<h4>Central Files</h4><ul>"
for file in insights['code']['central_files'][:5]:
html_response += f"<li><strong>{file['filename']}:</strong> {file['connections']} connections</li>"
html_response += "</ul>"
if 'frequently_modified_files' in insights['code']:
html_response += "<h4>Frequently Modified Files</h4><ul>"
for file in insights['code']['frequently_modified_files'][:5]:
html_response += f"<li><strong>{file['filename']}:</strong> {file['modifications']} modifications</li>"
html_response += "</ul>"
if 'file_types' in insights['code']:
html_response += "<h4>File Types</h4><ul>"
for file_type in insights['code']['file_types'][:5]:
ext = file_type['extension'] or 'No extension'
html_response += f"<li><strong>{ext}:</strong> {file_type['count']} files</li>"
html_response += "</ul>"
html_response += """
</div>
"""
if 'issues' in insights:
html_response += """
<div class="repository-card">
<h3>Issues</h3>
<div class="metric-container">
"""
# Add metrics
metrics = [
("Total Issues", insights['issues'].get('total_issues', 0)),
("Open Issues", insights['issues'].get('open_issues', 0)),
("Closed Issues", insights['issues'].get('closed_issues', 0)),
("Resolution Rate", f"{insights['issues'].get('resolution_rate', 0) * 100:.1f}%")
]
for label, value in metrics:
html_response += f"""
<div class="metric-card">
<div class="metric-label">{label}</div>
<div class="metric-value">{value}</div>
</div>
"""
html_response += """
</div>
"""
if 'avg_days_to_close' in insights['issues']:
html_response += f"<p><strong>Average Days to Close:</strong> {insights['issues']['avg_days_to_close']}</p>"
if 'top_labels' in insights['issues']:
html_response += "<h4>Top Labels</h4><ul>"
for label in insights['issues']['top_labels']:
html_response += f"<li><strong>{label['label']}:</strong> {label['count']} issues</li>"
html_response += "</ul>"
html_response += """
</div>
"""
return markdown.markdown(html_response)
else:
return f"Error: {result['message']}"
except Exception as e:
return f"Error: {str(e)}"
def show_visualizations() -> Tuple[str, str, str, str]:
"""Show repository visualizations (Gradio wrapper)"""
if not agent.repository_loaded:
return "No repository loaded.", "No repository loaded.","No repository loaded.","No repository loaded."
try:
result = agent.get_visualizations()
if result['success']:
visualizations = result['visualizations']
repo_graph_html = "<div class='repository-card'><p>No repository graph available.</p></div>"
activity_chart_html = "<div class='repository-card'><p>No activity chart available.</p></div>"
contributor_network_html = "<div class='repository-card'><p>No contributor network available.</p></div>"
dependency_graph_html = "<div class='repository-card'><p>No dependency graph available.</p></div>"
for viz_type, path in visualizations.items():
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
if viz_type == "repository_graph":
repo_graph_html = content
elif viz_type == "activity_chart":
activity_chart_html = content
elif viz_type == "contributor_network":
contributor_network_html = content
elif viz_type == "dependency_graph":
dependency_graph_html = content
return repo_graph_html, activity_chart_html, contributor_network_html, dependency_graph_html
else:
error_msg = f"<div class='repository-card'><p>Error: {result['message']}</p></div>"
return error_msg, error_msg, error_msg, error_msg
except Exception as e:
error_msg = f"<div class='repository-card'><p>Error: {str(e)}</p></div>"
return error_msg, error_msg, error_msg, error_msg
# Helper function to display API key tooltip
def api_key_tooltip():
return """
<div class="tooltip">
<i>ⓘ</i>
<span class="tooltiptext">Your API key is used securely and not stored permanently.</span>
</div>
"""
# Create the Gradio interface
with gr.Blocks(css=custom_css, title="GitHub Repository AI Agent") as interface:
gr.HTML("""
<div style="text-align: center; margin-bottom: 10px;">
<h1>GitHub Repository AI Agent</h1>
<p>Analyze repositories, understand code, identify collaborators, and extract insights with Gemini AI</p>
</div>
""")
# Theme toggle
with gr.Row():
with gr.Column(scale=1):
pass # Empty column for spacing
with gr.Column(scale=10):
theme_switch = gr.Checkbox(label="Dark Mode", value=False)
with gr.Column(scale=1):
pass # Empty column for spacing
with gr.Tab("Repository"):
with gr.Row():
with gr.Column():
gr.HTML("<h3>Repository Configuration</h3>")
repo_url = gr.Textbox(label="Repository URL", placeholder="https://github.com/username/repository")
gemini_api_key = gr.Textbox(label="Gemini API Key", type="password")
# Private repository toggle
repo_type = gr.Radio(
["Public Repository", "Private Repository"],
label="Repository Type",
value="Public Repository"
)
# GitHub token input (initially hidden)
with gr.Group(visible=False) as private_repo_group:
github_token = gr.Textbox(
label="GitHub Personal Access Token",
type="password",
placeholder="Enter your GitHub token for private repository access"
)
gr.HTML("<div style='font-size: 12px; color: #777;'>⚠️ Your token is used securely and not stored permanently.</div>")
load_btn = gr.Button("Load Repository", variant="primary")
# Loading status
load_status = gr.Textbox(label="Status", visible=True)
progress = gr.Slider(
minimum=0,
maximum=1,
value=0,
label="Loading Progress",
visible=True
)
repo_info = gr.HTML(label="Repository Information")
# Show/hide GitHub token based on repository type selection
repo_type.change(
fn=lambda x: gr.Group(visible=(x == "Private Repository")),
inputs=repo_type,
outputs=private_repo_group
)
# Load repository with progress updates
load_btn.click(
load_repository,
inputs=[
repo_url,
gemini_api_key,
# Pass whether to use token based on repo type
repo_type.change(lambda x: x == "Private Repository", repo_type),
github_token
],
outputs=[load_status, repo_info, progress]
)
with gr.Tab("Ask Questions"):
with gr.Row():
question = gr.Textbox(
label="Question",
placeholder="Ask about the repository (e.g., What's the main purpose of this repository?)",
lines=3
)
ask_btn = gr.Button("Ask", variant="primary")
answer = gr.HTML(label="Answer")
ask_btn.click(
ask_question,
inputs=question,
outputs=answer
)
with gr.Tab("Code Analysis"):
with gr.Row():
with gr.Column():
file_path = gr.Textbox(label="File Path", placeholder="path/to/file.py")
code_snippet = gr.Code(label="Or paste code snippet", language="python", lines=10)
analyze_btn = gr.Button("Analyze", variant="primary")
code_analysis = gr.HTML(label="Analysis")
analyze_btn.click(
analyze_code,
inputs=[file_path, code_snippet],
outputs=code_analysis
)
with gr.Tab("Find Collaborators"):
with gr.Row():
requirements = gr.Textbox(
label="Requirements",
placeholder="Describe what you're looking for in a collaborator (e.g., experience with machine learning, React development, etc.)",
lines=5
)
find_btn = gr.Button("Find Collaborators", variant="primary")
collaborators = gr.HTML(label="Potential Collaborators")
find_btn.click(
find_collaborators,
inputs=requirements,
outputs=collaborators
)
with gr.Tab("Insights"):
insights_btn = gr.Button("Get Repository Insights", variant="primary")
insights_html = gr.HTML(label="Repository Insights")
insights_btn.click(
get_insights,
inputs=[],
outputs=insights_html
)
with gr.Tab("Visualizations"):
viz_btn = gr.Button("Show Visualizations", variant="primary")
# Create tabs for visualizations
viz_tabs = gr.Tabs([
gr.TabItem("Repository Graph", gr.HTML(label="Repository Graph")),
gr.TabItem("Activity Chart", gr.HTML(label="Activity Chart")),
gr.TabItem("Contributor Network", gr.HTML(label="Contributor Network")),
gr.TabItem("Dependency Graph", gr.HTML(label="Dependency Graph"))
])
viz_btn.click(
show_visualizations,
inputs=[],
outputs=[
viz_tabs.select(0),
viz_tabs.select(1),
viz_tabs.select(2),
viz_tabs.select(3)
]
)
# Add theme toggle functionality
theme_switch.change(
fn=lambda x: gr.Blocks(css=custom_css + (".gradio-container {background-color: #2c3e50; color: #ecf0f1;}" if x else "")),
inputs=theme_switch,
outputs=interface
)
return interface
def main():
"""Main function for running the GitHub AI Agent"""
agent = GitHubAIAgent()
interface = create_web_interface(agent)
interface.launch(show_api=False, server_name="0.0.0.0", server_port=int(os.environ.get('PORT', 7860)))
if __name__ == "__main__":
main()