'use client';
/**
* BillSectionExplainer Component
*
* Provides on-demand plain language explanations for bill sections.
* Uses JIT (Just-In-Time) generation with Neo4j caching:
* - First click: Generates via Claude API and caches in Neo4j
* - Subsequent clicks: Instant response from cache
*/
import { useState } from 'react';
import { useLocale } from 'next-intl';
import Link from 'next/link';
import { Lightbulb, Loader2, ChevronDown, ChevronUp, Sparkles, Key } from 'lucide-react';
interface BillSectionExplainerProps {
session: string;
billNumber: string;
sectionId: string;
className?: string;
}
export function BillSectionExplainer({
session,
billNumber,
sectionId,
className = '',
}: BillSectionExplainerProps) {
const [explanation, setExplanation] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isExpanded, setIsExpanded] = useState(true);
const [wasCached, setWasCached] = useState<boolean | null>(null);
const [error, setError] = useState<string | null>(null);
const [errorCode, setErrorCode] = useState<string | null>(null);
const locale = useLocale();
const fetchExplanation = async () => {
if (explanation) {
// Already have explanation, just toggle visibility
setIsExpanded(!isExpanded);
return;
}
setIsLoading(true);
setError(null);
setErrorCode(null);
try {
const res = await fetch(
`/api/bills/${session}/${billNumber}/sections/${encodeURIComponent(sectionId)}/explanation`
);
if (!res.ok) {
const errorData = await res.json();
if (errorData.error_code) {
setErrorCode(errorData.error_code);
}
throw new Error(errorData.error || 'Failed to fetch explanation');
}
const data = await res.json();
setExplanation(data.explanation);
setWasCached(data.cached);
} catch (err) {
console.error('[BillSectionExplainer] Error:', err);
setError(err instanceof Error ? err.message : 'Failed to load explanation');
} finally {
setIsLoading(false);
}
};
return (
<div className={`mt-3 ${className}`}>
{/* Explain Button */}
<button
onClick={fetchExplanation}
disabled={isLoading}
className={`
flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-lg
transition-all duration-200
${explanation
? 'bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
: 'bg-gradient-to-r from-blue-500/10 to-purple-500/10 text-blue-400 hover:from-blue-500/20 hover:to-purple-500/20 border border-blue-500/20'
}
disabled:opacity-50 disabled:cursor-not-allowed
`}
>
{isLoading ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
<span>Generating explanation...</span>
</>
) : explanation ? (
<>
<Lightbulb className="w-4 h-4" />
<span>Plain language explanation</span>
{isExpanded ? (
<ChevronUp className="w-4 h-4 ml-1" />
) : (
<ChevronDown className="w-4 h-4 ml-1" />
)}
</>
) : (
<>
<Sparkles className="w-4 h-4" />
<span>Explain in plain language</span>
</>
)}
</button>
{/* Error Message */}
{error && (
<div className="mt-2 p-3 bg-red-500/10 border border-red-500/30 rounded-lg">
{errorCode === 'BYOK_KEY_REQUIRED' ? (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Key className="w-4 h-4 text-amber-400" />
<p className="text-sm text-amber-400 font-medium">API Key Required</p>
</div>
<p className="text-sm text-text-secondary">
Your BYOK plan requires an Anthropic API key to use AI features.
</p>
<Link
href={`/${locale}/settings`}
className="inline-flex items-center gap-1.5 text-sm text-blue-400 hover:text-blue-300 hover:underline"
>
<Key className="w-3.5 h-3.5" />
Add your API key in Settings
</Link>
</div>
) : (
<p className="text-sm text-red-400">{error}</p>
)}
</div>
)}
{/* Explanation Panel */}
{explanation && isExpanded && (
<div className="mt-3 p-4 bg-background-secondary rounded-lg border border-border-primary animate-in fade-in slide-in-from-top-2 duration-300">
<div className="flex items-center gap-2 mb-3 text-xs text-text-tertiary">
<Lightbulb className="w-3.5 h-3.5" />
<span>AI-generated plain language explanation</span>
{wasCached && (
<span className="px-1.5 py-0.5 bg-green-500/10 text-green-400 rounded text-[10px]">
cached
</span>
)}
</div>
<div className="text-sm text-text-secondary leading-relaxed whitespace-pre-wrap">
{explanation}
</div>
<p className="mt-3 text-[11px] text-text-tertiary italic">
This explanation is provided for informational purposes only and does not constitute legal advice.
</p>
</div>
)}
</div>
);
}
export default BillSectionExplainer;