// ======================================== // InsightDetailPanel Component // ======================================== // Display detailed view of a single insight with patterns, suggestions, and metadata import { useIntl } from 'react-intl'; import { cn } from '@/lib/utils'; import { X, Sparkles, Bot, Code2, Cpu, Trash2, AlertTriangle, Lightbulb, Clock, FileText, } from 'lucide-react'; import type { InsightHistory, Pattern, Suggestion } from '@/lib/api'; import { Button } from '@/components/ui/Button'; export interface InsightDetailPanelProps { /** Insight to display (null = panel hidden) */ insight: InsightHistory | null; /** Called when close button clicked */ onClose: () => void; /** Called when delete button clicked */ onDelete?: (insightId: string) => void; /** Is delete operation in progress */ isDeleting?: boolean; /** Optional className */ className?: string; } // Tool icon mapping const toolConfig = { gemini: { icon: Sparkles, color: 'text-blue-500', bgColor: 'bg-blue-500/10', label: 'Gemini', }, qwen: { icon: Bot, color: 'text-purple-500', bgColor: 'bg-purple-500/10', label: 'Qwen', }, codex: { icon: Code2, color: 'text-green-500', bgColor: 'bg-green-500/10', label: 'Codex', }, default: { icon: Cpu, color: 'text-gray-500', bgColor: 'bg-gray-500/10', label: 'CLI', }, }; // Severity configuration const severityConfig = { error: { badge: 'bg-red-500/10 text-red-500 border-red-500/20', border: 'border-l-red-500', dot: 'bg-red-500', }, warning: { badge: 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20', border: 'border-l-yellow-500', dot: 'bg-yellow-500', }, info: { badge: 'bg-blue-500/10 text-blue-500 border-blue-500/20', border: 'border-l-blue-500', dot: 'bg-blue-500', }, default: { badge: 'bg-gray-500/10 text-gray-500 border-gray-500/20', border: 'border-l-gray-500', dot: 'bg-gray-500', }, }; // Suggestion type configuration const suggestionTypeConfig = { refactor: { badge: 'bg-purple-500/10 text-purple-500 border-purple-500/20', icon: 'refactor', }, optimize: { badge: 'bg-green-500/10 text-green-500 border-green-500/20', icon: 'optimize', }, fix: { badge: 'bg-red-500/10 text-red-500 border-red-500/20', icon: 'fix', }, document: { badge: 'bg-blue-500/10 text-blue-500 border-blue-500/20', icon: 'document', }, }; /** * Format timestamp to relative time */ function formatRelativeTime(timestamp: string, locale: string): string { const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (locale === 'zh') { if (diffMins < 1) return '刚刚'; if (diffMins < 60) return `${diffMins}分钟前`; if (diffHours < 24) return `${diffHours}小时前`; return `${diffDays}天前`; } if (diffMins < 1) return 'just now'; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; return `${diffDays}d ago`; } /** * PatternItem component for displaying a single pattern */ function PatternItem({ pattern }: { pattern: Pattern; locale: string }) { const severity = pattern.severity ?? 'info'; const config = severityConfig[severity] ?? severityConfig.default; return (
{pattern.name?.split(' ')[0] || 'Pattern'} {severity}

{pattern.description}

{pattern.example && (
{pattern.example}
)}
); } /** * SuggestionItem component for displaying a single suggestion */ function SuggestionItem({ suggestion }: { suggestion: Suggestion; locale: string }) { const { formatMessage } = useIntl(); const config = suggestionTypeConfig[suggestion.type] ?? suggestionTypeConfig.refactor; const typeLabel = formatMessage({ id: `prompts.suggestions.types.${suggestion.type}` }); return (
{typeLabel} {suggestion.effort && ( {formatMessage({ id: 'prompts.suggestions.effort' })}: {suggestion.effort} )}

{suggestion.title}

{suggestion.description}

{suggestion.code && (
{suggestion.code}
)}
); } /** * InsightDetailPanel component - Display full insight details */ export function InsightDetailPanel({ insight, onClose, onDelete, isDeleting = false, className, }: InsightDetailPanelProps) { const { formatMessage } = useIntl(); const locale = useIntl().locale; // Don't render if no insight if (!insight) { return null; } const config = toolConfig[insight.tool as keyof typeof toolConfig] ?? toolConfig.default; const ToolIcon = config.icon; const timeAgo = formatRelativeTime(insight.created_at, locale); const patternCount = insight.patterns?.length ?? 0; const suggestionCount = insight.suggestions?.length ?? 0; return (
{/* Header */}

{formatMessage({ id: 'prompts.insightDetail.title' })}

{/* Scrollable content */}
{/* Metadata */}
{config.label}
{timeAgo}
{insight.prompt_count} {formatMessage({ id: 'prompts.insightDetail.promptsAnalyzed' })}
{/* Patterns */} {insight.patterns && insight.patterns.length > 0 && (

{formatMessage({ id: 'prompts.insightDetail.patterns' })} ({patternCount})

{insight.patterns.map((pattern) => ( ))}
)} {/* Suggestions */} {insight.suggestions && insight.suggestions.length > 0 && (

{formatMessage({ id: 'prompts.insightDetail.suggestions' })} ({suggestionCount})

{insight.suggestions.map((suggestion) => ( ))}
)} {/* Empty state */} {(!insight.patterns || insight.patterns.length === 0) && (!insight.suggestions || insight.suggestions.length === 0) && (
{formatMessage({ id: 'prompts.insightDetail.noContent' })}
)}
{/* Footer actions */} {onDelete && (
)}
); } /** * InsightDetailPanelOverlay - Full screen overlay with panel */ export interface InsightDetailPanelOverlayProps extends InsightDetailPanelProps { /** Show overlay backdrop */ showOverlay?: boolean; } export function InsightDetailPanelOverlay({ insight, onClose, onDelete, isDeleting = false, showOverlay = true, className, }: InsightDetailPanelOverlayProps) { if (!insight) { return null; } return ( <> {showOverlay && (
)} ); } export default InsightDetailPanel;