MoHamdyy's picture
updated retrevial logic
691964f
import gradio as gr # type:ignore
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", # "github config"
"deep neural networks", "deep learning", "neural network", # Changed to spaces
"tensorflow", "pytorch", "ml",
"distributed systems", # Changed to spaces
"web development", "mobile development", "game development", "machine learning",
"data science", "artificial intelligence", "devops", "cybersecurity", "blockchain",
"iot", "cloud computing", "big data", "robotics", "bioinformatics", "ar vr", # "ar vr" might be better as "augmented reality", "virtual reality" or keep as is if it's a common tag
"natural language processing", "computer vision", "data visualization",
"react", "angular", "vue", "nextjs", "nodejs", "svelte",
"django", "flask", "spring", "dotnet", "ruby on rails", # "ruby on rails"
"android", "ios", "flutter", "react native", # "react native"
"scikit learn", "keras", "pandas", "numpy", # "scikit learn"
"docker", "kubernetes", "aws", "azure", "google cloud platform", "serverless", # "google cloud platform"
"sql", "nosql", "mongodb", "postgresql", "mysql", "graphql",
"api", "gui", "testing", "documentation", "education", "accessibility",
"raspberry pi", "arduino", "linux", "windows", "macos", "gaming", "graphics", "fintech" # "raspberry pi"
])))
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" # Svelte/Vue also as languages for their specific file types
])
def find_and_suggest_issues(
selected_language: str | None, # From language dropdown
selected_curated_topics: list[str] | None, # From topics dropdown (multiselect)
custom_topics_str: str | None # From topics textbox
):
print(f"Gradio app received language: '{selected_language}', curated_topics: {selected_curated_topics}, custom_topics: '{custom_topics_str}'")
# issues_markdown, llm_suggestion, raw_issues_state,
# dropdown_update, button_update, controls_section_update, display_section_update,
# language_searched_state
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),
"" # language_searched_state
)
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: # Language is now from a dropdown, should always have a value if user interacts
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() # Already a slug from dropdown
# --- Combine curated and custom topics ---
final_topics_set = set()
if selected_curated_topics: # This will be a list from multiselect dropdown
for topic in selected_curated_topics:
if topic and topic.strip():
final_topics_set.add(topic.strip().lower()) # Already slugs
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) # Add directly, github_client handles quoting if needed
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 # Fetch 5 issues
)
if fetched_issues_list is None: # GitHub API call failed
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: # No issues found
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( # Pass language_to_search
issues_for_llm, language_to_search, target_count=1
# additional_prompt_context for uncommon language is removed
)
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) # Return the searched language for state
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.") # MODIFIED
with gr.Row():
with gr.Column(scale=1): # Input column
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, # Default to python or first in list
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): # Output column
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...")
# --- Using INITIAL_CHECKLIST_ITEMS constant for choices ---
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 # Starts hidden
)
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], # UPDATED
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] # Targets CheckboxGroup
)
if __name__ == "__main__":
print("Launching ContribNavigator Gradio App...")
demo.launch()