Add application file
Browse files
app.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr # type:ignore
|
| 2 |
+
import os
|
| 3 |
+
from core.github_client import fetch_beginner_issues
|
| 4 |
+
from core.llm_handler import get_simple_issue_suggestion, plan_onboarding_kit_components
|
| 5 |
+
from core.kit_generator import generate_kit_from_plan
|
| 6 |
+
import utils.config_loader
|
| 7 |
+
|
| 8 |
+
# --- NEW/UPDATED CONSTANTS ---
|
| 9 |
+
# (Incorporating your suggestions and expanding slightly)
|
| 10 |
+
CURATED_TOPIC_SLUGS = sorted(list(set([
|
| 11 |
+
# Your suggestions (some are single, some multi-word)
|
| 12 |
+
"javascript", "css", "config", "python", "html", "cli", "typescript", "tailwindcss", "github config", "llm", # "github config"
|
| 13 |
+
"deep neural networks", "deep learning", "neural network", # Changed to spaces
|
| 14 |
+
"tensorflow", "pytorch", "ml",
|
| 15 |
+
"distributed systems", # Changed to spaces
|
| 16 |
+
|
| 17 |
+
# Broad Categories (changed to spaces where appropriate)
|
| 18 |
+
"web development", "mobile development", "game development", "machine learning",
|
| 19 |
+
"data science", "artificial intelligence", "devops", "cybersecurity", "blockchain",
|
| 20 |
+
"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
|
| 21 |
+
"natural language processing", "computer vision", "data visualization",
|
| 22 |
+
# Specific Technologies & Frameworks
|
| 23 |
+
"react", "angular", "vue", "nextjs", "nodejs", "svelte",
|
| 24 |
+
"django", "flask", "spring", "dotnet", "ruby on rails", # "ruby on rails"
|
| 25 |
+
"android", "ios", "flutter", "react native", # "react native"
|
| 26 |
+
"scikit learn", "keras", "pandas", "numpy", # "scikit learn"
|
| 27 |
+
"docker", "kubernetes", "aws", "azure", "google cloud platform", "serverless", # "google cloud platform"
|
| 28 |
+
"sql", "nosql", "mongodb", "postgresql", "mysql", "graphql",
|
| 29 |
+
"api", "gui", "testing", "documentation", "education", "accessibility",
|
| 30 |
+
"raspberry pi", "arduino", "linux", "windows", "macos", "gaming", "graphics", "fintech" # "raspberry pi"
|
| 31 |
+
])))
|
| 32 |
+
|
| 33 |
+
CURATED_LANGUAGE_SLUGS = sorted([
|
| 34 |
+
"python", "javascript", "java", "c#", "c++", "c", "go", "rust", "ruby", "php",
|
| 35 |
+
"swift", "kotlin", "typescript", "html", "css", "sql", "r", "perl", "scala",
|
| 36 |
+
"haskell", "lua", "dart", "elixir", "clojure", "objective-c", "shell", "powershell",
|
| 37 |
+
"assembly", "matlab", "groovy", "julia", "ocaml", "pascal", "fortran", "lisp",
|
| 38 |
+
"prolog", "erlang", "f#", "zig", "nim", "crystal", "svelte", "vue" # Svelte/Vue also as languages for their specific file types
|
| 39 |
+
])
|
| 40 |
+
# --- END NEW/UPDATED CONSTANTS ---
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# --- MODIFIED FUNCTION: find_and_suggest_issues ---
|
| 44 |
+
def find_and_suggest_issues(
|
| 45 |
+
selected_language: str | None, # From language dropdown
|
| 46 |
+
selected_curated_topics: list[str] | None, # From topics dropdown (multiselect)
|
| 47 |
+
custom_topics_str: str | None # From topics textbox
|
| 48 |
+
):
|
| 49 |
+
print(f"Gradio app received language: '{selected_language}', curated_topics: {selected_curated_topics}, custom_topics: '{custom_topics_str}'")
|
| 50 |
+
|
| 51 |
+
# --- Default error/empty returns for 8 outputs ---
|
| 52 |
+
# issues_markdown, llm_suggestion, raw_issues_state,
|
| 53 |
+
# dropdown_update, button_update, controls_section_update, display_section_update,
|
| 54 |
+
# language_searched_state
|
| 55 |
+
empty_error_return = (
|
| 56 |
+
"Error or no input.", None, None,
|
| 57 |
+
gr.update(choices=[], value=None, visible=False), gr.update(visible=False),
|
| 58 |
+
gr.update(visible=False), gr.update(visible=False),
|
| 59 |
+
"" # language_searched_state
|
| 60 |
+
)
|
| 61 |
+
no_issues_found_return_factory = lambda lang, topics_str: (
|
| 62 |
+
f"No beginner-friendly issues found for '{lang}'" +
|
| 63 |
+
(f" with topics '{topics_str}'" if topics_str else "") +
|
| 64 |
+
" using current labels. Try different criteria.",
|
| 65 |
+
None, None,
|
| 66 |
+
gr.update(choices=[], value=None, visible=False), gr.update(visible=False),
|
| 67 |
+
gr.update(visible=False), gr.update(visible=False),
|
| 68 |
+
lang or ""
|
| 69 |
+
)
|
| 70 |
+
# ---
|
| 71 |
+
|
| 72 |
+
if not selected_language: # Language is now from a dropdown, should always have a value if user interacts
|
| 73 |
+
return ("Please select a programming language.", None, None,
|
| 74 |
+
gr.update(choices=[], value=None, visible=False), gr.update(visible=False),
|
| 75 |
+
gr.update(visible=False), gr.update(visible=False),
|
| 76 |
+
"")
|
| 77 |
+
|
| 78 |
+
language_to_search = selected_language.strip().lower() # Already a slug from dropdown
|
| 79 |
+
|
| 80 |
+
# --- Combine curated and custom topics ---
|
| 81 |
+
final_topics_set = set()
|
| 82 |
+
if selected_curated_topics: # This will be a list from multiselect dropdown
|
| 83 |
+
for topic in selected_curated_topics:
|
| 84 |
+
if topic and topic.strip():
|
| 85 |
+
final_topics_set.add(topic.strip().lower()) # Already slugs
|
| 86 |
+
if custom_topics_str:
|
| 87 |
+
custom_topics_list = [ct.strip().lower() for ct in custom_topics_str.split(',') if ct.strip()]
|
| 88 |
+
for topic in custom_topics_list:
|
| 89 |
+
final_topics_set.add(topic) # Add directly, github_client handles quoting if needed
|
| 90 |
+
|
| 91 |
+
final_topics_list = list(final_topics_set) if final_topics_set else None
|
| 92 |
+
print(f"Final parsed topics for search: {final_topics_list}")
|
| 93 |
+
# --- End Combine topics ---
|
| 94 |
+
|
| 95 |
+
# --- REMOVED: is_common_language and language_warning_for_llm logic ---
|
| 96 |
+
# Since language comes from a curated dropdown, we assume it's "common" or valid.
|
| 97 |
+
# The GitHub API will be the ultimate judge if it finds anything.
|
| 98 |
+
|
| 99 |
+
fetched_issues_list = fetch_beginner_issues(
|
| 100 |
+
language_to_search,
|
| 101 |
+
topics=final_topics_list,
|
| 102 |
+
per_page=5 # Fetch 5 issues
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
if fetched_issues_list is None: # GitHub API call failed
|
| 106 |
+
return ("Error: Could not fetch issues from GitHub. Check server logs.", None, None,
|
| 107 |
+
gr.update(choices=[], value=None, visible=False), gr.update(visible=False),
|
| 108 |
+
gr.update(visible=False), gr.update(visible=False),
|
| 109 |
+
language_to_search)
|
| 110 |
+
|
| 111 |
+
if not fetched_issues_list: # No issues found
|
| 112 |
+
return no_issues_found_return_factory(language_to_search, ", ".join(final_topics_list) if final_topics_list else None)
|
| 113 |
+
|
| 114 |
+
# --- REMOVED: issues_markdown_prefix related to uncommon language ---
|
| 115 |
+
# This is no longer needed if language is from a curated dropdown.
|
| 116 |
+
|
| 117 |
+
issues_display_list = []
|
| 118 |
+
issue_titles_for_dropdown = []
|
| 119 |
+
for i, issue in enumerate(fetched_issues_list[:5]): # Display up to 5
|
| 120 |
+
title = issue.get('title', 'N/A')
|
| 121 |
+
issues_display_list.append(
|
| 122 |
+
f"{i+1}. **{title}**\n"
|
| 123 |
+
f" - Repo: [{issue.get('repository_html_url', '#')}]({issue.get('repository_html_url', '#')})\n"
|
| 124 |
+
f" - URL: [{issue.get('html_url', '#')}]({issue.get('html_url', '#')})\n"
|
| 125 |
+
f" - Labels: {', '.join(issue.get('labels', []))}\n"
|
| 126 |
+
)
|
| 127 |
+
issue_titles_for_dropdown.append(f"{i+1}. {title}")
|
| 128 |
+
issues_markdown = "\n---\n".join(issues_display_list)
|
| 129 |
+
|
| 130 |
+
issues_for_llm = fetched_issues_list[:3]
|
| 131 |
+
llm_suggestion_text = "Could not get LLM suggestion at this moment."
|
| 132 |
+
if issues_for_llm and utils.config_loader.OPENAI_API_KEY:
|
| 133 |
+
suggestion = get_simple_issue_suggestion( # Pass language_to_search
|
| 134 |
+
issues_for_llm, language_to_search, target_count=1
|
| 135 |
+
# additional_prompt_context for uncommon language is removed
|
| 136 |
+
)
|
| 137 |
+
if suggestion: llm_suggestion_text = f"**🤖 AI Navigator's Suggestion:**\n\n{suggestion}"
|
| 138 |
+
else: llm_suggestion_text = "LLM processed the request but gave an empty response or an error occurred."
|
| 139 |
+
elif not utils.config_loader.OPENAI_API_KEY:
|
| 140 |
+
llm_suggestion_text = "OpenAI API Key not configured. LLM suggestion skipped."
|
| 141 |
+
elif not issues_for_llm :
|
| 142 |
+
llm_suggestion_text = "No issues were available to provide a suggestion for."
|
| 143 |
+
|
| 144 |
+
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)
|
| 145 |
+
kit_button_visibility_update = gr.update(visible=True)
|
| 146 |
+
kit_controls_section_update = gr.update(visible=True)
|
| 147 |
+
kit_display_section_update = gr.update(visible=True)
|
| 148 |
+
|
| 149 |
+
return (issues_markdown, llm_suggestion_text, fetched_issues_list,
|
| 150 |
+
kit_dropdown_update, kit_button_visibility_update,
|
| 151 |
+
kit_controls_section_update, kit_display_section_update,
|
| 152 |
+
language_to_search) # Return the searched language for state
|
| 153 |
+
# --- END MODIFIED FUNCTION ---
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
# handle_kit_generation function (This function should be your last correct version)
|
| 157 |
+
# ... (Ensure your full handle_kit_generation is here)
|
| 158 |
+
def handle_kit_generation(selected_issue_title_with_num: str, current_issues_state: list[dict], language_searched_state: str ):
|
| 159 |
+
checklist_update_on_error = gr.update(value=[], visible=False)
|
| 160 |
+
if not selected_issue_title_with_num or not current_issues_state:
|
| 161 |
+
return "Please select an issue first...", checklist_update_on_error
|
| 162 |
+
if not language_searched_state:
|
| 163 |
+
language_searched_state = "the project's primary language"
|
| 164 |
+
selected_issue_obj = None
|
| 165 |
+
try:
|
| 166 |
+
for i, issue_in_state in enumerate(current_issues_state):
|
| 167 |
+
numbered_title_in_state = f"{i+1}. {issue_in_state.get('title', 'N/A')}"
|
| 168 |
+
if numbered_title_in_state == selected_issue_title_with_num:
|
| 169 |
+
selected_issue_obj = issue_in_state
|
| 170 |
+
break
|
| 171 |
+
if not selected_issue_obj:
|
| 172 |
+
return f"Error: Could not find data for issue '{selected_issue_title_with_num}'.", checklist_update_on_error
|
| 173 |
+
plan_response = plan_onboarding_kit_components(selected_issue_obj, language_searched_state)
|
| 174 |
+
if not plan_response or "error" in plan_response:
|
| 175 |
+
error_detail = plan_response.get("details", "") if plan_response else "Planner None"
|
| 176 |
+
return f"Error planning kit: {plan_response.get('error', 'Unknown')}. {error_detail}", checklist_update_on_error
|
| 177 |
+
components_to_include = plan_response.get("include_components", [])
|
| 178 |
+
if not components_to_include:
|
| 179 |
+
return "AI planner decided no kit components needed.", checklist_update_on_error
|
| 180 |
+
kit_markdown_content = generate_kit_from_plan(selected_issue_obj, language_searched_state, components_to_include)
|
| 181 |
+
checklist_update_on_success = gr.update(value=[], visible=True)
|
| 182 |
+
return kit_markdown_content, checklist_update_on_success
|
| 183 |
+
except Exception as e:
|
| 184 |
+
import traceback
|
| 185 |
+
traceback.print_exc()
|
| 186 |
+
return f"Unexpected error generating kit: {str(e)}", checklist_update_on_error
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 190 |
+
gr.Markdown("# 🤖 ContribNavigator: Your AI Guide to Open Source Contributions")
|
| 191 |
+
gr.Markdown("Select a programming language and optional topics to find beginner-friendly open source issues.") # MODIFIED
|
| 192 |
+
|
| 193 |
+
with gr.Row():
|
| 194 |
+
with gr.Column(scale=1): # Input column
|
| 195 |
+
# --- MODIFIED Language Input to Dropdown ---
|
| 196 |
+
lang_dropdown_input = gr.Dropdown(
|
| 197 |
+
label="Programming Language (*)",
|
| 198 |
+
choices=CURATED_LANGUAGE_SLUGS,
|
| 199 |
+
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
|
| 200 |
+
interactive=True,
|
| 201 |
+
# allow_custom_value=True # Consider this if you want users to type languages not in the list
|
| 202 |
+
)
|
| 203 |
+
# --- END MODIFIED Language Input ---
|
| 204 |
+
|
| 205 |
+
# --- NEW/MODIFIED Topics Input ---
|
| 206 |
+
curated_topics_dropdown = gr.Dropdown(
|
| 207 |
+
label="Select Common Topics (Optional, Multi-Select)",
|
| 208 |
+
choices=CURATED_TOPIC_SLUGS,
|
| 209 |
+
multiselect=True,
|
| 210 |
+
interactive=True
|
| 211 |
+
)
|
| 212 |
+
custom_topics_input = gr.Textbox(
|
| 213 |
+
label="Or, Add Custom Topics (Optional, comma-separated slugs)",
|
| 214 |
+
placeholder="e.g., my-niche-topic, another-custom-tag"
|
| 215 |
+
)
|
| 216 |
+
# --- END NEW/MODIFIED Topics Input ---
|
| 217 |
+
|
| 218 |
+
find_button = gr.Button("🔍 Find Beginner Issues", variant="primary")
|
| 219 |
+
|
| 220 |
+
with gr.Column(visible=False) as kit_controls_section:
|
| 221 |
+
selected_issue_dropdown = gr.Dropdown(
|
| 222 |
+
label="Select an Issue to Generate Kit:", choices=[], interactive=True, visible=True
|
| 223 |
+
)
|
| 224 |
+
generate_kit_button = gr.Button("🛠️ Generate Onboarding Kit", visible=False)
|
| 225 |
+
|
| 226 |
+
with gr.Column(scale=2): # Output column
|
| 227 |
+
gr.Markdown("## Recommended Issues:")
|
| 228 |
+
issues_output = gr.Markdown(value="Your recommended issues will appear here...")
|
| 229 |
+
gr.Markdown("## Navigator's Insights:")
|
| 230 |
+
llm_suggestion_output = gr.Markdown(value="AI-powered suggestions will appear here...")
|
| 231 |
+
|
| 232 |
+
with gr.Column(visible=False) as kit_display_section:
|
| 233 |
+
gr.Markdown("## 📖 Your Onboarding Kit:")
|
| 234 |
+
kit_output = gr.Markdown("Your onboarding kit will appear here...")
|
| 235 |
+
# --- Using INITIAL_CHECKLIST_ITEMS constant for choices ---
|
| 236 |
+
INITIAL_CHECKLIST_ITEMS = [
|
| 237 |
+
"Understand the Issue: Read the issue description carefully.",
|
| 238 |
+
"Explore the Repository: Use the 'Quick Look' section in the kit to get familiar.",
|
| 239 |
+
"Read Contribution Guidelines: Review the project's contribution rules and setup (see kit).",
|
| 240 |
+
"Clone the Repository: Get the code on your local machine (see kit for command).",
|
| 241 |
+
"Set Up Development Environment: Follow any setup instructions in the guidelines.",
|
| 242 |
+
"Create a New Branch: For your changes (e.g., `git checkout -b my-fix-for-issue-123`).",
|
| 243 |
+
"Make Initial Contact (Optional but good): Leave a comment on the GitHub issue expressing your interest.",
|
| 244 |
+
"Start Investigating/Coding!",
|
| 245 |
+
"Ask Questions: If you're stuck, don't hesitate to ask for help on the issue or project's communication channels."
|
| 246 |
+
]
|
| 247 |
+
checklist_group_output = gr.CheckboxGroup(
|
| 248 |
+
label="✅ Your First Steps Checklist:",
|
| 249 |
+
choices=INITIAL_CHECKLIST_ITEMS,
|
| 250 |
+
value=[],
|
| 251 |
+
interactive=True,
|
| 252 |
+
visible=False # Starts hidden
|
| 253 |
+
)
|
| 254 |
+
# --- END Using INITIAL_CHECKLIST_ITEMS ---
|
| 255 |
+
|
| 256 |
+
raw_issues_state = gr.State([])
|
| 257 |
+
language_searched_state = gr.State("")
|
| 258 |
+
|
| 259 |
+
# --- MODIFIED find_button.click inputs ---
|
| 260 |
+
find_button.click(
|
| 261 |
+
fn=find_and_suggest_issues,
|
| 262 |
+
inputs=[lang_dropdown_input, curated_topics_dropdown, custom_topics_input], # UPDATED
|
| 263 |
+
outputs=[
|
| 264 |
+
issues_output, llm_suggestion_output, raw_issues_state,
|
| 265 |
+
selected_issue_dropdown, generate_kit_button,
|
| 266 |
+
kit_controls_section, kit_display_section,
|
| 267 |
+
language_searched_state
|
| 268 |
+
]
|
| 269 |
+
)
|
| 270 |
+
# --- END MODIFIED find_button.click ---
|
| 271 |
+
|
| 272 |
+
generate_kit_button.click(
|
| 273 |
+
fn=handle_kit_generation,
|
| 274 |
+
inputs=[selected_issue_dropdown, raw_issues_state, language_searched_state],
|
| 275 |
+
outputs=[kit_output, checklist_group_output] # Targets CheckboxGroup
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
if __name__ == "__main__":
|
| 279 |
+
print("Launching ContribNavigator Gradio App...")
|
| 280 |
+
demo.launch()
|