# 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', '
') 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"""
{badge_text}

Repository: {result['repo_data']['full_name']}

Description: {result['repo_data']['description'] or "No description available"}

Stars
{result['repo_data']['stars']}
Forks
{result['repo_data']['forks']}
Language
{result['repo_data']['language'] or "N/A"}
Files
{result['file_count']}
Contributors
{result['contributor_count']}

Created: {created_at} | Last Updated: {updated_at}

License: {result['repo_data'].get('license', 'None')}

Topics

{', '.join(result['repo_data'].get('topics', ['None'])) if result['repo_data'].get('topics') else 'None'}

Repository Analysis

{analysis_content}
""" # 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', '
') html_response = f"""

Answer

{answer_content}
""" if result['relevant_files']: html_response += f"""

Relevant Files:

" html_response += "
" 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', '
') html_response = f"""

Code Analysis

{"File" if file_path else "Code Snippet"}: {file_path if file_path else "Provided snippet"}

{analysis_content}
""" 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 = "

Potential Collaborators

" 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"""

{collaborator['login']}

Confidence: {confidence_percent}

Reasons:

""" 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 = "

Repository Insights

" if 'basic_stats' in insights: html_response += """

Basic Statistics

""" if 'activity' in insights: html_response += """

Activity

""" # 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"""
{label}
{value}
""" html_response += """
""" if 'contributors' in insights: html_response += """

Contributors

""" # 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"""
{label}
{value}
""" html_response += """
""" if 'top_contributors' in insights['contributors']: html_response += "

Top Contributors

" html_response += """
""" if 'code' in insights: html_response += """

Code

""" if 'central_files' in insights['code']: html_response += "

Central Files

" if 'frequently_modified_files' in insights['code']: html_response += "

Frequently Modified Files

" if 'file_types' in insights['code']: html_response += "

File Types

" html_response += """
""" if 'issues' in insights: html_response += """

Issues

""" # 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"""
{label}
{value}
""" html_response += """
""" if 'avg_days_to_close' in insights['issues']: html_response += f"

Average Days to Close: {insights['issues']['avg_days_to_close']}

" if 'top_labels' in insights['issues']: html_response += "

Top Labels

" html_response += """
""" 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 = "

No repository graph available.

" activity_chart_html = "

No activity chart available.

" contributor_network_html = "

No contributor network available.

" dependency_graph_html = "

No dependency graph available.

" 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"

Error: {result['message']}

" return error_msg, error_msg, error_msg, error_msg except Exception as e: error_msg = f"

Error: {str(e)}

" return error_msg, error_msg, error_msg, error_msg # Helper function to display API key tooltip def api_key_tooltip(): return """
Your API key is used securely and not stored permanently.
""" # Create the Gradio interface with gr.Blocks(css=custom_css, title="GitHub Repository AI Agent") as interface: gr.HTML("""

GitHub Repository AI Agent

Analyze repositories, understand code, identify collaborators, and extract insights with Gemini AI

""") # 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("

Repository Configuration

") 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("
⚠️ Your token is used securely and not stored permanently.
") 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()