File size: 13,761 Bytes
59c2194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691964f
59c2194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691964f
59c2194
 
 
691964f
59c2194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
691964f
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
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()