Spaces:
Sleeping
Sleeping
| """ | |
| GitHub Issue Triager | |
| Agentic workflow: Analyze β Classify β Prioritize β Assign | |
| """ | |
| import gradio as gr | |
| from huggingface_hub import InferenceClient | |
| import json | |
| import os | |
| import sys | |
| import time | |
| sys.path.append(os.path.join(os.path.dirname(__file__), '..')) | |
| from shared.components import create_method_panel, create_premium_hero | |
| # Initialize client | |
| client = InferenceClient(model="meta-llama/Llama-3.3-70B-Instruct") | |
| def triage_issue(issue_text: str) -> dict: | |
| """Analyze and triage a GitHub issue""" | |
| if not os.getenv("HF_TOKEN"): | |
| text = issue_text.lower() | |
| labels = [] | |
| if any(word in text for word in ["crash", "error", "bug", "broken", "fails", "failure"]): | |
| labels.append("bug") | |
| if any(word in text for word in ["slow", "latency", "performance", "timeout"]): | |
| labels.append("performance") | |
| if any(word in text for word in ["docs", "readme", "documentation"]): | |
| labels.append("documentation") | |
| if any(word in text for word in ["feature", "request", "support", "add"]): | |
| labels.append("feature") | |
| if not labels: | |
| labels = ["question"] | |
| priority = "P1" if any(word in text for word in ["data loss", "security", "cannot login", "down", "crash"]) else "P2" | |
| severity = "High" if priority == "P1" else "Medium" | |
| title = issue_text.strip().splitlines()[0][:90] if issue_text.strip() else "Issue report" | |
| return { | |
| "title": title, | |
| "labels": labels[:4], | |
| "priority": priority, | |
| "severity": severity, | |
| "category": "Backend" if any(word in text for word in ["api", "database", "server"]) else "Frontend" if any(word in text for word in ["ui", "button", "page"]) else "Other", | |
| "estimated_effort": "Medium (1-3 days)" if priority == "P1" else "Small (< 1 day)", | |
| "suggested_assignee": "Maintainer review", | |
| "reasoning": { | |
| "label_reasoning": "Deterministic fallback based on issue keywords because HF_TOKEN is not configured.", | |
| "priority_reasoning": "Priority estimated from impact words such as crash, security, or data loss.", | |
| "effort_reasoning": "Fallback estimate; refine after reproduction." | |
| }, | |
| "related_areas": ["triage", "reproduction"], | |
| "next_steps": ["Reproduce the issue", "Confirm expected behavior", "Assign owner", "Add regression test if this is a bug"] | |
| } | |
| prompt = f"""You are a GitHub issue triager. Analyze this issue and provide structured triage information. | |
| ISSUE: | |
| {issue_text} | |
| Provide a JSON response with: | |
| {{ | |
| "title": "Brief title extracted from issue", | |
| "labels": ["bug", "feature", "question", "documentation", "enhancement", "performance"], | |
| "priority": "P0/P1/P2/P3", | |
| "severity": "Critical/High/Medium/Low", | |
| "category": "Frontend/Backend/DevOps/Documentation/Other", | |
| "estimated_effort": "Small (< 1 day)/Medium (1-3 days)/Large (> 3 days)", | |
| "suggested_assignee": "Team/person based on issue type", | |
| "reasoning": {{ | |
| "label_reasoning": "Why these labels", | |
| "priority_reasoning": "Why this priority (consider: impact, urgency, user-facing)", | |
| "effort_reasoning": "Why this effort estimate" | |
| }}, | |
| "related_areas": ["area1", "area2"], | |
| "next_steps": ["Step 1", "Step 2"] | |
| }} | |
| Priority definitions: | |
| - P0: Critical bug, service down, data loss | |
| - P1: Major bug, significant feature broken, high user impact | |
| - P2: Minor bug, feature request with clear value | |
| - P3: Nice-to-have, low impact | |
| Choose only the most relevant labels (2-4 max).""" | |
| response = "" | |
| for message in client.chat_completion( | |
| messages=[{"role": "user", "content": prompt}], | |
| max_tokens=1200, | |
| stream=True, | |
| ): | |
| response += message.choices[0].delta.content or "" | |
| # Extract JSON | |
| try: | |
| if "```json" in response: | |
| response = response.split("```json")[1].split("```")[0].strip() | |
| elif "```" in response: | |
| response = response.split("```")[1].split("```")[0].strip() | |
| data = json.loads(response) | |
| return data | |
| except Exception as e: | |
| return { | |
| "title": "Error parsing issue", | |
| "labels": [], | |
| "priority": "P3", | |
| "severity": "Low", | |
| "category": "Other", | |
| "estimated_effort": "Unknown", | |
| "suggested_assignee": "TBD", | |
| "reasoning": { | |
| "label_reasoning": "Error occurred", | |
| "priority_reasoning": "Error occurred", | |
| "effort_reasoning": "Error occurred" | |
| }, | |
| "related_areas": [], | |
| "next_steps": [] | |
| } | |
| def process_triage(issue_text: str, progress=gr.Progress()): | |
| """Main issue triage workflow""" | |
| if not issue_text.strip(): | |
| return "Please paste an issue description." | |
| # Step 1: Analyze issue | |
| progress(0.2, desc="Analyzing issue content...") | |
| time.sleep(0.3) | |
| # Step 2: Classify and prioritize | |
| progress(0.5, desc="Classifying and prioritizing...") | |
| triage = triage_issue(issue_text) | |
| # Step 3: Generate recommendations | |
| progress(0.8, desc="Generating recommendations...") | |
| time.sleep(0.3) | |
| # Format output | |
| priority_emoji = { | |
| "P0": "π΄", | |
| "P1": "π ", | |
| "P2": "π‘", | |
| "P3": "π’" | |
| } | |
| severity_emoji = { | |
| "Critical": "π¨", | |
| "High": "β οΈ", | |
| "Medium": "π", | |
| "Low": "βΉοΈ" | |
| } | |
| output = f"# Issue Triage Report\n\n" | |
| output += f"## {triage['title']}\n\n" | |
| # Priority and Severity | |
| output += "### π― Priority & Severity\n\n" | |
| p_emoji = priority_emoji.get(triage['priority'], "βͺ") | |
| s_emoji = severity_emoji.get(triage['severity'], "βΉοΈ") | |
| output += f"- **Priority**: {p_emoji} **{triage['priority']}**\n" | |
| output += f"- **Severity**: {s_emoji} {triage['severity']}\n" | |
| output += f"- **Category**: {triage['category']}\n" | |
| output += f"- **Estimated Effort**: {triage['estimated_effort']}\n\n" | |
| # Labels | |
| output += "### π·οΈ Suggested Labels\n\n" | |
| for label in triage['labels']: | |
| # Color code labels | |
| label_colors = { | |
| "bug": "π΄", | |
| "feature": "π¦", | |
| "question": "π£", | |
| "documentation": "π", | |
| "enhancement": "β¨", | |
| "performance": "β‘" | |
| } | |
| emoji = label_colors.get(label, "π") | |
| output += f"- {emoji} `{label}`\n" | |
| output += "\n" | |
| # Assignment | |
| output += "### π€ Suggested Assignment\n\n" | |
| output += f"**{triage['suggested_assignee']}**\n\n" | |
| # Related Areas | |
| if triage['related_areas']: | |
| output += "### π Related Areas\n\n" | |
| for area in triage['related_areas']: | |
| output += f"- {area}\n" | |
| output += "\n" | |
| # Reasoning | |
| output += "### π‘ Reasoning\n\n" | |
| output += f"**Labels**: {triage['reasoning']['label_reasoning']}\n\n" | |
| output += f"**Priority**: {triage['reasoning']['priority_reasoning']}\n\n" | |
| output += f"**Effort**: {triage['reasoning']['effort_reasoning']}\n\n" | |
| # Next Steps | |
| if triage['next_steps']: | |
| output += "### β Recommended Next Steps\n\n" | |
| for i, step in enumerate(triage['next_steps'], 1): | |
| output += f"{i}. {step}\n" | |
| output += "\n" | |
| output += "---\n*Generated by Issue Triager Agent*\n" | |
| progress(1.0, desc="Complete!") | |
| return output | |
| # Gradio Interface | |
| with gr.Blocks(theme=gr.themes.Soft(), title="GitHub Issue Triager") as demo: | |
| create_premium_hero( | |
| "GitHub Issue Triager Agent", | |
| "Convert messy issue reports into labels, severity, priority, ownership suggestions, and reasoning.", | |
| "π«", | |
| badge="Developer Operations", | |
| highlights=["Structured triage", "Reasoned labels", "Agent workflow"], | |
| ) | |
| create_method_panel({ | |
| "Workflow": "Issue text β classification prompt β JSON parsing β priority and routing report.", | |
| "What it proves": "You can turn LLM output into operational structure, not just prose.", | |
| "HF capability": "Runs as a lightweight Space over Hub-hosted instruction models.", | |
| }) | |
| with gr.Row(): | |
| with gr.Column(): | |
| issue_input = gr.Textbox( | |
| label="GitHub Issue", | |
| placeholder="Paste issue title and description...", | |
| lines=12 | |
| ) | |
| triage_btn = gr.Button("π Triage Issue", variant="primary", size="lg") | |
| gr.Examples( | |
| examples=[ | |
| ["""Title: App crashes on startup after login | |
| When I try to log in, the app crashes immediately. This started happening after the latest update (v2.1.0). I'm on iPhone 14, iOS 17. | |
| Steps to reproduce: | |
| 1. Open app | |
| 2. Enter credentials | |
| 3. Tap login | |
| 4. App crashes | |
| Expected: Should show dashboard | |
| Actual: App crashes to home screen | |
| This is blocking all users from accessing the app."""], | |
| ["""Title: Add dark mode support | |
| It would be great to have a dark mode option in the settings. Many users prefer dark mode for night-time usage and it reduces eye strain. | |
| This has been requested by multiple users in our Discord community."""], | |
| ["""Title: Documentation unclear for API authentication | |
| The docs for setting up API authentication are confusing. The example code doesn't work and there's no explanation of where to find the API key. | |
| Could we update the docs with a clearer example?"""], | |
| ], | |
| inputs=issue_input | |
| ) | |
| with gr.Row(): | |
| output = gr.Markdown(label="Triage Report") | |
| triage_btn.click( | |
| fn=process_triage, | |
| inputs=[issue_input], | |
| outputs=[output] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |