Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import asyncio | |
| from typing import List, Dict, Any, Optional | |
| from loguru import logger | |
| import json | |
| import os | |
| # Import core components | |
| from ui.backend import DebugBackend, LocalBackend | |
| from core.models import RankedSolution | |
| from visualization.blaxel_generator import ErrorFlowVisualizer | |
| from voice.elevenlabs_tts import VoiceExplainer | |
| from config.api_keys import api_config | |
| from theme import debuggenie_theme | |
| class DebugGenieUI: | |
| def __init__(self, backend: DebugBackend): | |
| self.backend = backend | |
| self.visualizer = ErrorFlowVisualizer() | |
| # Initialize voice explainer if API key is available | |
| try: | |
| self.voice_explainer = VoiceExplainer(api_key=api_config.elevenlabs_api_key) | |
| except: | |
| logger.warning("ElevenLabs API key not found - voice features disabled") | |
| self.voice_explainer = None | |
| async def handle_analyze( | |
| self, | |
| error_text: str, | |
| screenshot, | |
| codebase_files, | |
| progress=gr.Progress() | |
| ): | |
| """Main analysis handler with progressive updates.""" | |
| try: | |
| # Validate inputs | |
| if not error_text and screenshot is None: | |
| return ( | |
| "<div style='padding: 20px; text-align: center; color: #666;'>β οΈ Please provide an error message or screenshot</div>", | |
| "<div style='padding: 20px; text-align: center; color: #999;'>Solutions will appear here after analysis</div>", | |
| "<div style='padding: 20px; text-align: center; color: #999;'>Visualization will appear here</div>", | |
| None, | |
| None | |
| ) | |
| progress(0.2, desc="Analyzing error...") | |
| # Build context | |
| context = { | |
| 'error_text': error_text, | |
| 'image': screenshot, | |
| 'code_context': "" | |
| } | |
| if screenshot is not None: | |
| context['type'] = 'ide' | |
| progress(0.5, desc="Running AI analysis...") | |
| # Run backend analysis | |
| result = await self.backend.analyze(context) | |
| progress(0.8, desc="Generating results...") | |
| # Build root cause display | |
| root_cause_html = f""" | |
| <div style='background: #f8f9fa; border-left: 4px solid #4f46e5; padding: 20px; border-radius: 8px; margin-bottom: 20px;'> | |
| <h3 style='margin: 0 0 10px 0; color: #1e293b;'>π― Root Cause</h3> | |
| <p style='margin: 0; color: #475569; line-height: 1.6;'>{result.root_cause}</p> | |
| </div> | |
| """ | |
| # Generate solutions HTML | |
| solutions_html = self._generate_solutions_html(result.solutions) | |
| # Generate visualization | |
| mock_trace = self.visualizer.generate_mock_trace() | |
| viz_html = self.visualizer.generate_flow(mock_trace) | |
| # Generate voice explanation | |
| voice_audio = None | |
| if self.voice_explainer and result.solutions: | |
| try: | |
| top_solution = result.solutions[0] | |
| ranked_sol = RankedSolution( | |
| rank=1, | |
| title=top_solution.get('title', 'Solution'), | |
| description=top_solution.get('description', ''), | |
| steps=[], | |
| confidence=top_solution.get('probability', 0.5), | |
| sources=[], | |
| why_ranked_here=f"Top solution with {top_solution.get('probability', 0)*100:.0f}% confidence", | |
| trade_offs=[] | |
| ) | |
| audio_bytes = self.voice_explainer.generate_explanation(ranked_sol, mode="walkthrough") | |
| if audio_bytes: | |
| voice_path = self.voice_explainer.save_audio( | |
| audio_bytes, | |
| f"explanation_{hash(error_text[:100])}.mp3" | |
| ) | |
| voice_audio = voice_path | |
| except Exception as e: | |
| logger.warning(f"Voice generation failed: {e}") | |
| progress(1.0, desc="Complete!") | |
| return ( | |
| root_cause_html, | |
| solutions_html, | |
| viz_html, | |
| voice_audio, | |
| { | |
| "execution_time": f"{result.execution_time:.2f}s", | |
| "confidence": f"{result.confidence_score:.1%}", | |
| "solutions_found": len(result.solutions) | |
| } | |
| ) | |
| except Exception as e: | |
| logger.error(f"Analysis failed: {e}") | |
| return ( | |
| f"<div style='padding: 20px; background: #fee; border-radius: 8px; color: #c00;'>β Analysis failed: {str(e)}</div>", | |
| "", | |
| "", | |
| None, | |
| {"error": str(e)} | |
| ) | |
| def _generate_solutions_html(self, solutions: List[Dict]) -> str: | |
| """Generate clean, readable solutions display.""" | |
| if not solutions: | |
| return "<div style='padding: 20px; text-align: center; color: #999;'>No solutions found</div>" | |
| html = "<div style='display: flex; flex-direction: column; gap: 16px;'>" | |
| for idx, sol in enumerate(solutions[:5], 1): | |
| title = sol.get('title', f'Solution {idx}') | |
| desc = sol.get('description', 'No description available') | |
| prob = sol.get('probability', 0.5) | |
| # Color based on confidence | |
| if prob > 0.7: | |
| bg_color = "#10b981" | |
| badge_text = "High Confidence" | |
| elif prob > 0.4: | |
| bg_color = "#f59e0b" | |
| badge_text = "Medium Confidence" | |
| else: | |
| bg_color = "#6366f1" | |
| badge_text = "Low Confidence" | |
| html += f""" | |
| <div style='border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; background: white;'> | |
| <div style='display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;'> | |
| <div style='display: flex; align-items: center; gap: 12px;'> | |
| <span style='display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; background: {bg_color}; color: white; border-radius: 50%; font-weight: bold; font-size: 16px;'>{idx}</span> | |
| <h4 style='margin: 0; color: #1e293b; font-size: 18px;'>{title}</h4> | |
| </div> | |
| <span style='background: {bg_color}20; color: {bg_color}; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: 600;'>{prob:.0%} β’ {badge_text}</span> | |
| </div> | |
| <p style='margin: 0; color: #64748b; line-height: 1.6;'>{desc}</p> | |
| </div> | |
| """ | |
| html += "</div>" | |
| return html | |
| def create_interface(backend: DebugBackend): | |
| """Create the main Gradio interface with clean, simple design.""" | |
| ui = DebugGenieUI(backend) | |
| with gr.Blocks(title="DebugGenie - AI Debugging Assistant") as demo: | |
| # Header | |
| gr.Markdown( | |
| """ | |
| # π§ DebugGenie | |
| ### AI-Powered Debugging Assistant | |
| Paste your error message below and get instant solutions from multiple AI agents. | |
| """, | |
| elem_classes="header" | |
| ) | |
| # Main input section | |
| gr.Markdown("## π Enter Your Error") | |
| error_input = gr.Code( | |
| label="Error Message or Stack Trace", | |
| language="python", | |
| lines=10, | |
| placeholder="Paste your error message, stack trace, or exception here...", | |
| show_label=False | |
| ) | |
| # Optional inputs in accordion | |
| with gr.Accordion("β Add Screenshot or Files (Optional)", open=False): | |
| with gr.Row(): | |
| screenshot_input = gr.Image( | |
| label="Screenshot", | |
| type="pil", | |
| sources=["upload", "clipboard"] | |
| ) | |
| codebase_files = gr.File( | |
| label="Related Code Files", | |
| file_count="multiple" | |
| ) | |
| # Analyze button | |
| analyze_btn = gr.Button( | |
| "π Analyze Error", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| # Results section | |
| gr.Markdown("## π― Analysis Results") | |
| root_cause_display = gr.HTML( | |
| label="Root Cause", | |
| value="<div style='padding: 20px; text-align: center; color: #999;'>Results will appear here after analysis</div>" | |
| ) | |
| # Solutions in accordion | |
| with gr.Accordion("π‘ Recommended Solutions", open=True): | |
| solutions_display = gr.HTML( | |
| value="<div style='padding: 20px; text-align: center; color: #999;'>Solutions will appear here</div>" | |
| ) | |
| # Additional details in tabs | |
| with gr.Tabs(): | |
| with gr.Tab("π Error Visualization"): | |
| viz_display = gr.HTML() | |
| with gr.Tab("ποΈ Voice Explanation"): | |
| voice_output = gr.Audio( | |
| label="AI-Generated Explanation", | |
| autoplay=False | |
| ) | |
| with gr.Tab("π Technical Details"): | |
| analysis_json = gr.JSON(label="Analysis Metrics") | |
| # Examples section | |
| gr.Markdown("### π Try These Examples") | |
| gr.Examples( | |
| examples=[ | |
| [ | |
| """Traceback (most recent call last): | |
| File "app.py", line 42, in process_data | |
| result = json.loads(data) | |
| json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)""", | |
| None, | |
| None | |
| ], | |
| [ | |
| """TypeError: 'NoneType' object is not subscriptable | |
| File "main.py", line 15, in get_user | |
| return users[user_id]['name']""", | |
| None, | |
| None | |
| ], | |
| [ | |
| """AttributeError: 'list' object has no attribute 'keys' | |
| File "data_processor.py", line 28 | |
| for key in config.keys():""", | |
| None, | |
| None | |
| ] | |
| ], | |
| inputs=[error_input, screenshot_input, codebase_files], | |
| label=None | |
| ) | |
| # Event handler | |
| analyze_btn.click( | |
| fn=ui.handle_analyze, | |
| inputs=[error_input, screenshot_input, codebase_files], | |
| outputs=[ | |
| root_cause_display, | |
| solutions_display, | |
| viz_display, | |
| voice_output, | |
| analysis_json | |
| ] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| backend = LocalBackend() | |
| demo = create_interface(backend) | |
| demo.launch( | |
| server_name="127.0.0.1", | |
| server_port=7860, | |
| share=False, | |
| show_error=True, | |
| theme=gr.themes.Soft( | |
| primary_hue="indigo", | |
| radius_size="lg" | |
| ), | |
| css=""" | |
| /* Clean, minimal styling */ | |
| .header { | |
| text-align: center; | |
| padding: 24px 0; | |
| margin-bottom: 32px; | |
| } | |
| .header h1 { | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| margin-bottom: 8px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .header h3 { | |
| color: #64748b; | |
| font-weight: 400; | |
| } | |
| /* Container spacing */ | |
| .gradio-container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| /* Button styling */ | |
| button.primary { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border: none; | |
| font-weight: 600; | |
| } | |
| button.primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3); | |
| } | |
| /* Smooth transitions */ | |
| * { | |
| transition: all 0.2s ease; | |
| } | |
| """ | |
| ) |