File size: 15,232 Bytes
59c2194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
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

# --- NEW/UPDATED CONSTANTS ---
# (Incorporating your suggestions and expanding slightly)
CURATED_TOPIC_SLUGS = sorted(list(set([
    # Your suggestions (some are single, some multi-word)
    "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

    # Broad Categories (changed to spaces where appropriate)
    "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",
    # Specific Technologies & Frameworks
    "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
])
# --- END NEW/UPDATED CONSTANTS ---


# --- MODIFIED FUNCTION: find_and_suggest_issues ---
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}'")

    # --- Default error/empty returns for 8 outputs ---
    # 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}")
    # --- End Combine topics ---

    # --- REMOVED: is_common_language and language_warning_for_llm logic ---
    # Since language comes from a curated dropdown, we assume it's "common" or valid.
    # The GitHub API will be the ultimate judge if it finds anything.

    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)

    # --- REMOVED: issues_markdown_prefix related to uncommon language ---
    # This is no longer needed if language is from a curated dropdown.

    issues_display_list = []
    issue_titles_for_dropdown = []
    for i, issue in enumerate(fetched_issues_list[:5]): # Display up to 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
# --- END MODIFIED FUNCTION ---


# handle_kit_generation function (This function should be your last correct version)
# ... (Ensure your full handle_kit_generation is here)
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
            # --- MODIFIED Language Input to Dropdown ---
            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,
                # allow_custom_value=True # Consider this if you want users to type languages not in the list
            )
            # --- END MODIFIED Language Input ---

            # --- NEW/MODIFIED Topics Input ---
            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"
            )
            # --- END NEW/MODIFIED Topics Input ---

            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
                )
                # --- END Using INITIAL_CHECKLIST_ITEMS ---

    raw_issues_state = gr.State([])
    language_searched_state = gr.State("")

    # --- MODIFIED find_button.click inputs ---
    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
        ]
    )
    # --- END MODIFIED find_button.click ---

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