|
import gradio as gr |
|
import os |
|
from core.github_client import fetch_beginner_issues |
|
from core.llm_handler import get_simple_issue_suggestion, plan_onboarding_kit_components |
|
from core.kit_generator import generate_kit_from_plan |
|
import utils.config_loader |
|
|
|
CURATED_TOPIC_SLUGS = sorted(list(set([ |
|
"javascript", "css", "config", "python", "html", "cli", "typescript", "tailwindcss", "github config", "llm", |
|
"deep neural networks", "deep learning", "neural network", |
|
"tensorflow", "pytorch", "ml", |
|
"distributed systems", |
|
|
|
"web development", "mobile development", "game development", "machine learning", |
|
"data science", "artificial intelligence", "devops", "cybersecurity", "blockchain", |
|
"iot", "cloud computing", "big data", "robotics", "bioinformatics", "ar vr", |
|
"natural language processing", "computer vision", "data visualization", |
|
"react", "angular", "vue", "nextjs", "nodejs", "svelte", |
|
"django", "flask", "spring", "dotnet", "ruby on rails", |
|
"android", "ios", "flutter", "react native", |
|
"scikit learn", "keras", "pandas", "numpy", |
|
"docker", "kubernetes", "aws", "azure", "google cloud platform", "serverless", |
|
"sql", "nosql", "mongodb", "postgresql", "mysql", "graphql", |
|
"api", "gui", "testing", "documentation", "education", "accessibility", |
|
"raspberry pi", "arduino", "linux", "windows", "macos", "gaming", "graphics", "fintech" |
|
]))) |
|
|
|
CURATED_LANGUAGE_SLUGS = sorted([ |
|
"python", "javascript", "java", "c#", "c++", "c", "go", "rust", "ruby", "php", |
|
"swift", "kotlin", "typescript", "html", "css", "sql", "r", "perl", "scala", |
|
"haskell", "lua", "dart", "elixir", "clojure", "objective-c", "shell", "powershell", |
|
"assembly", "matlab", "groovy", "julia", "ocaml", "pascal", "fortran", "lisp", |
|
"prolog", "erlang", "f#", "zig", "nim", "crystal", "svelte", "vue" |
|
]) |
|
|
|
|
|
|
|
def find_and_suggest_issues( |
|
selected_language: str | None, |
|
selected_curated_topics: list[str] | None, |
|
custom_topics_str: str | None |
|
): |
|
print(f"Gradio app received language: '{selected_language}', curated_topics: {selected_curated_topics}, custom_topics: '{custom_topics_str}'") |
|
|
|
|
|
|
|
|
|
empty_error_return = ( |
|
"Error or no input.", None, None, |
|
gr.update(choices=[], value=None, visible=False), gr.update(visible=False), |
|
gr.update(visible=False), gr.update(visible=False), |
|
"" |
|
) |
|
no_issues_found_return_factory = lambda lang, topics_str: ( |
|
f"No beginner-friendly issues found for '{lang}'" + |
|
(f" with topics '{topics_str}'" if topics_str else "") + |
|
" using current labels. Try different criteria.", |
|
None, None, |
|
gr.update(choices=[], value=None, visible=False), gr.update(visible=False), |
|
gr.update(visible=False), gr.update(visible=False), |
|
lang or "" |
|
) |
|
|
|
if not selected_language: |
|
return ("Please select a programming language.", None, None, |
|
gr.update(choices=[], value=None, visible=False), gr.update(visible=False), |
|
gr.update(visible=False), gr.update(visible=False), |
|
"") |
|
|
|
language_to_search = selected_language.strip().lower() |
|
|
|
|
|
final_topics_set = set() |
|
if selected_curated_topics: |
|
for topic in selected_curated_topics: |
|
if topic and topic.strip(): |
|
final_topics_set.add(topic.strip().lower()) |
|
if custom_topics_str: |
|
custom_topics_list = [ct.strip().lower() for ct in custom_topics_str.split(',') if ct.strip()] |
|
for topic in custom_topics_list: |
|
final_topics_set.add(topic) |
|
|
|
final_topics_list = list(final_topics_set) if final_topics_set else None |
|
print(f"Final parsed topics for search: {final_topics_list}") |
|
|
|
fetched_issues_list = fetch_beginner_issues( |
|
language_to_search, |
|
topics=final_topics_list, |
|
per_page=5 |
|
) |
|
|
|
if fetched_issues_list is None: |
|
return ("Error: Could not fetch issues from GitHub. Check server logs.", None, None, |
|
gr.update(choices=[], value=None, visible=False), gr.update(visible=False), |
|
gr.update(visible=False), gr.update(visible=False), |
|
language_to_search) |
|
|
|
if not fetched_issues_list: |
|
return no_issues_found_return_factory(language_to_search, ", ".join(final_topics_list) if final_topics_list else None) |
|
|
|
|
|
|
|
issues_display_list = [] |
|
issue_titles_for_dropdown = [] |
|
for i, issue in enumerate(fetched_issues_list[:5]): |
|
title = issue.get('title', 'N/A') |
|
issues_display_list.append( |
|
f"{i+1}. **{title}**\n" |
|
f" - Repo: [{issue.get('repository_html_url', '#')}]({issue.get('repository_html_url', '#')})\n" |
|
f" - URL: [{issue.get('html_url', '#')}]({issue.get('html_url', '#')})\n" |
|
f" - Labels: {', '.join(issue.get('labels', []))}\n" |
|
) |
|
issue_titles_for_dropdown.append(f"{i+1}. {title}") |
|
issues_markdown = "\n---\n".join(issues_display_list) |
|
|
|
issues_for_llm = fetched_issues_list[:3] |
|
llm_suggestion_text = "Could not get LLM suggestion at this moment." |
|
if issues_for_llm and utils.config_loader.OPENAI_API_KEY: |
|
suggestion = get_simple_issue_suggestion( |
|
issues_for_llm, language_to_search, target_count=1 |
|
|
|
) |
|
if suggestion: llm_suggestion_text = f"**π€ AI Navigator's Suggestion:**\n\n{suggestion}" |
|
else: llm_suggestion_text = "LLM processed the request but gave an empty response or an error occurred." |
|
elif not utils.config_loader.OPENAI_API_KEY: |
|
llm_suggestion_text = "OpenAI API Key not configured. LLM suggestion skipped." |
|
elif not issues_for_llm : |
|
llm_suggestion_text = "No issues were available to provide a suggestion for." |
|
|
|
kit_dropdown_update = gr.update(choices=issue_titles_for_dropdown, value=issue_titles_for_dropdown[0] if issue_titles_for_dropdown else None, visible=True) |
|
kit_button_visibility_update = gr.update(visible=True) |
|
kit_controls_section_update = gr.update(visible=True) |
|
kit_display_section_update = gr.update(visible=True) |
|
|
|
return (issues_markdown, llm_suggestion_text, fetched_issues_list, |
|
kit_dropdown_update, kit_button_visibility_update, |
|
kit_controls_section_update, kit_display_section_update, |
|
language_to_search) |
|
|
|
def handle_kit_generation(selected_issue_title_with_num: str, current_issues_state: list[dict], language_searched_state: str ): |
|
checklist_update_on_error = gr.update(value=[], visible=False) |
|
if not selected_issue_title_with_num or not current_issues_state: |
|
return "Please select an issue first...", checklist_update_on_error |
|
if not language_searched_state: |
|
language_searched_state = "the project's primary language" |
|
selected_issue_obj = None |
|
try: |
|
for i, issue_in_state in enumerate(current_issues_state): |
|
numbered_title_in_state = f"{i+1}. {issue_in_state.get('title', 'N/A')}" |
|
if numbered_title_in_state == selected_issue_title_with_num: |
|
selected_issue_obj = issue_in_state |
|
break |
|
if not selected_issue_obj: |
|
return f"Error: Could not find data for issue '{selected_issue_title_with_num}'.", checklist_update_on_error |
|
plan_response = plan_onboarding_kit_components(selected_issue_obj, language_searched_state) |
|
if not plan_response or "error" in plan_response: |
|
error_detail = plan_response.get("details", "") if plan_response else "Planner None" |
|
return f"Error planning kit: {plan_response.get('error', 'Unknown')}. {error_detail}", checklist_update_on_error |
|
components_to_include = plan_response.get("include_components", []) |
|
if not components_to_include: |
|
return "AI planner decided no kit components needed.", checklist_update_on_error |
|
kit_markdown_content = generate_kit_from_plan(selected_issue_obj, language_searched_state, components_to_include) |
|
checklist_update_on_success = gr.update(value=[], visible=True) |
|
return kit_markdown_content, checklist_update_on_success |
|
except Exception as e: |
|
import traceback |
|
traceback.print_exc() |
|
return f"Unexpected error generating kit: {str(e)}", checklist_update_on_error |
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft()) as demo: |
|
gr.Markdown("# π€ ContribNavigator: Your AI Guide to Open Source Contributions") |
|
gr.Markdown("Select a programming language and optional topics to find beginner-friendly open source issues.") |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
lang_dropdown_input = gr.Dropdown( |
|
label="Programming Language (*)", |
|
choices=CURATED_LANGUAGE_SLUGS, |
|
value=CURATED_LANGUAGE_SLUGS[CURATED_LANGUAGE_SLUGS.index("python")] if "python" in CURATED_LANGUAGE_SLUGS else CURATED_LANGUAGE_SLUGS[0] if CURATED_LANGUAGE_SLUGS else None, |
|
interactive=True, |
|
) |
|
|
|
curated_topics_dropdown = gr.Dropdown( |
|
label="Select Common Topics (Optional, Multi-Select)", |
|
choices=CURATED_TOPIC_SLUGS, |
|
multiselect=True, |
|
interactive=True |
|
) |
|
custom_topics_input = gr.Textbox( |
|
label="Or, Add Custom Topics (Optional, comma-separated slugs)", |
|
placeholder="e.g., my-niche-topic, another-custom-tag" |
|
) |
|
|
|
find_button = gr.Button("π Find Beginner Issues", variant="primary") |
|
|
|
with gr.Column(visible=False) as kit_controls_section: |
|
selected_issue_dropdown = gr.Dropdown( |
|
label="Select an Issue to Generate Kit:", choices=[], interactive=True, visible=True |
|
) |
|
generate_kit_button = gr.Button("π οΈ Generate Onboarding Kit", visible=False) |
|
|
|
with gr.Column(scale=2): |
|
gr.Markdown("## Recommended Issues:") |
|
issues_output = gr.Markdown(value="Your recommended issues will appear here...") |
|
gr.Markdown("## Navigator's Insights:") |
|
llm_suggestion_output = gr.Markdown(value="AI-powered suggestions will appear here...") |
|
|
|
with gr.Column(visible=False) as kit_display_section: |
|
gr.Markdown("## π Your Onboarding Kit:") |
|
kit_output = gr.Markdown("Your onboarding kit will appear here...") |
|
|
|
INITIAL_CHECKLIST_ITEMS = [ |
|
"Understand the Issue: Read the issue description carefully.", |
|
"Explore the Repository: Use the 'Quick Look' section in the kit to get familiar.", |
|
"Read Contribution Guidelines: Review the project's contribution rules and setup (see kit).", |
|
"Clone the Repository: Get the code on your local machine (see kit for command).", |
|
"Set Up Development Environment: Follow any setup instructions in the guidelines.", |
|
"Create a New Branch: For your changes (e.g., `git checkout -b my-fix-for-issue-123`).", |
|
"Make Initial Contact (Optional but good): Leave a comment on the GitHub issue expressing your interest.", |
|
"Start Investigating/Coding!", |
|
"Ask Questions: If you're stuck, don't hesitate to ask for help on the issue or project's communication channels." |
|
] |
|
checklist_group_output = gr.CheckboxGroup( |
|
label="β
Your First Steps Checklist:", |
|
choices=INITIAL_CHECKLIST_ITEMS, |
|
value=[], |
|
interactive=True, |
|
visible=False |
|
) |
|
|
|
raw_issues_state = gr.State([]) |
|
language_searched_state = gr.State("") |
|
|
|
find_button.click( |
|
fn=find_and_suggest_issues, |
|
inputs=[lang_dropdown_input, curated_topics_dropdown, custom_topics_input], |
|
outputs=[ |
|
issues_output, llm_suggestion_output, raw_issues_state, |
|
selected_issue_dropdown, generate_kit_button, |
|
kit_controls_section, kit_display_section, |
|
language_searched_state |
|
] |
|
) |
|
|
|
generate_kit_button.click( |
|
fn=handle_kit_generation, |
|
inputs=[selected_issue_dropdown, raw_issues_state, language_searched_state], |
|
outputs=[kit_output, checklist_group_output] |
|
) |
|
|
|
if __name__ == "__main__": |
|
print("Launching ContribNavigator Gradio App...") |
|
demo.launch() |