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