/** * ExplainButton Component * Reusable inline "Explain" button for configs, logs, and errors * Shows modal/popover with LLM-powered explanation */ import React, { useState } from 'react'; const ExplainButton = ({ type = 'config', // config, log, error, scan_result content, context = {}, size = 'small', style = {} }) => { const [isLoading, setIsLoading] = useState(false); const [showModal, setShowModal] = useState(false); const [explanation, setExplanation] = useState(null); const [error, setError] = useState(null); const handleExplain = async () => { setIsLoading(true); setError(null); setShowModal(true); try { const response = await fetch('/api/explain', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type, content, context }) }); if (!response.ok) { throw new Error('Failed to get explanation'); } const data = await response.json(); setExplanation(data); } catch (err) { console.error('Error getting explanation:', err); setError('Failed to load explanation. Please try again.'); } finally { setIsLoading(false); } }; const closeModal = () => { setShowModal(false); setExplanation(null); setError(null); }; const buttonSizes = { small: { padding: '4px 8px', fontSize: '12px' }, medium: { padding: '6px 12px', fontSize: '14px' }, large: { padding: '8px 16px', fontSize: '16px' } }; const buttonStyle = { ...buttonSizes[size], backgroundColor: '#3498DB', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: '4px', transition: 'background-color 0.2s', ...style }; const renderExplanation = () => { if (error) { return (
{error}
); } if (isLoading) { return (
Generating explanation...
); } if (!explanation) { return null; } // Render based on explanation type switch (type) { case 'config': return (

{explanation.config_key || 'Configuration'}

Current Value: {explanation.current_value}
What it does:

{explanation.description}

{explanation.example && (
Example:

{explanation.example}

)} {explanation.value_analysis && (
Analysis: {explanation.value_analysis}
)} {explanation.recommendations && explanation.recommendations.length > 0 && (
Recommendations:
)}
{explanation.requires_restart && (
⚠️ Changing this setting requires a restart
)} {!explanation.safe_to_change && (
⚠️ Use caution when changing this setting
)}
); case 'error': return (

Error Explanation

Original Error:
{explanation.original_error}
What went wrong:

{explanation.plain_english}

Likely causes:
💡 How to fix it:
    {explanation.suggested_fixes?.map((fix, i) => (
  1. {fix}
  2. ))}
Severity: {(explanation.severity || 'unknown').toUpperCase()}
); case 'log': return (

Log Entry Explanation

{explanation.log_entry}
Level: {explanation.log_level}
{explanation.timestamp && (
Time: {explanation.timestamp}
)}
What this means:

{explanation.explanation}

{explanation.action_needed && explanation.next_steps && explanation.next_steps.length > 0 && (
⚠️ Action needed:
)}
); default: return (
{explanation.explanation || 'No explanation available.'}
); } }; return ( <> {showModal && (
e.stopPropagation()} >

Explanation

{renderExplanation()}
)} ); }; export default ExplainButton;