debuggenie / ui /gradio_interface.py
Nihal2000's picture
Update ui/gradio_interface.py
e88eae3 verified
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;
}
"""
)