/**
* CognitiveResultModal Component
*
* Modal for displaying results from cognitive tool operations.
* Supports different result types: summarize, transform, find-similar, analyze.
*
* Requirements: 9.2, 9.3, 9.4, 9.5
*/
import { useCallback } from 'react';
import type {
AnalyzeResult,
CognitiveToolResult,
FindSimilarResult,
SummarizeResult,
TransformResult,
} from './CognitiveToolsPanel';
// ============================================================================
// Types
// ============================================================================
export interface CognitiveResultModalProps {
/** The result to display */
result: CognitiveToolResult;
/** Callback to close the modal */
onClose: () => void;
/** Callback when user wants to navigate to a similar memory */
onNavigateToMemory?: (memoryId: string) => void;
/** Additional CSS classes */
className?: string;
}
// ============================================================================
// Helper Functions
// ============================================================================
/**
* Format a number as percentage
*/
function formatPercentage(value: number): string {
return `${String(Math.round(value * 100))}%`;
}
/**
* Get severity color class for bias
*/
function getSeverityColorClass(severity: number): string {
if (severity >= 0.7) return 'text-red-400 bg-red-500/20 border-red-500/50';
if (severity >= 0.4) return 'text-yellow-400 bg-yellow-500/20 border-yellow-500/50';
return 'text-green-400 bg-green-500/20 border-green-500/50';
}
/**
* Get risk level color class
*/
function getRiskColorClass(risk: 'low' | 'medium' | 'high'): string {
switch (risk) {
case 'high':
return 'text-red-400';
case 'medium':
return 'text-yellow-400';
case 'low':
return 'text-green-400';
}
}
// ============================================================================
// Sub-Components
// ============================================================================
interface ModalOverlayProps {
children: React.ReactNode;
onClose: () => void;
}
/**
* Modal overlay with backdrop
*/
function ModalOverlay({ children, onClose }: ModalOverlayProps): React.ReactElement {
const handleBackdropClick = useCallback(
(e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
},
[onClose]
);
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
onClick={handleBackdropClick}
role="dialog"
aria-modal="true"
>
{children}
</div>
);
}
interface ModalContentProps {
title: string;
children: React.ReactNode;
onClose: () => void;
}
/**
* Modal content container with glassmorphism styling
*/
function ModalContent({ title, children, onClose }: ModalContentProps): React.ReactElement {
return (
<div
className="
bg-ui-surface
backdrop-blur-glass
border border-ui-border
rounded-lg
shadow-glow
max-w-2xl w-full mx-4
max-h-[80vh] overflow-hidden
flex flex-col
"
style={{
boxShadow: `
0 0 30px rgba(0, 255, 255, 0.2),
inset 0 0 40px rgba(0, 255, 255, 0.05)
`,
}}
>
{/* Header */}
<div className="flex justify-between items-center p-4 border-b border-ui-border">
<h2 className="text-lg font-semibold text-ui-accent-primary">{title}</h2>
<button
onClick={onClose}
className="p-1 hover:bg-ui-border rounded transition-colors text-ui-text-secondary hover:text-ui-text-primary"
aria-label="Close modal"
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
</div>
{/* Body */}
<div className="p-4 overflow-y-auto flex-1">{children}</div>
</div>
);
}
// ============================================================================
// Result Display Components
// ============================================================================
interface SummarizeResultDisplayProps {
result: SummarizeResult;
}
/**
* Display summarize result
* Requirements: 9.2
*/
function SummarizeResultDisplay({ result }: SummarizeResultDisplayProps): React.ReactElement {
return (
<div className="space-y-4">
{/* Summary */}
<div>
<h3 className="text-sm font-medium text-ui-accent-secondary mb-2">Summary</h3>
<p className="text-sm text-ui-text-primary whitespace-pre-wrap bg-ui-background/50 p-3 rounded-lg">
{result.summary}
</p>
</div>
{/* Insights */}
{result.insights.length > 0 && (
<div>
<h3 className="text-sm font-medium text-ui-accent-secondary mb-2">Key Insights</h3>
<ul className="space-y-2">
{result.insights.map((insight, index) => (
<li key={index} className="text-sm text-ui-text-primary flex items-start gap-2">
<span className="text-ui-accent-primary mt-0.5">•</span>
<span>{insight}</span>
</li>
))}
</ul>
</div>
)}
{/* Confidence */}
<div className="flex items-center gap-2 text-sm">
<span className="text-ui-text-secondary">Confidence:</span>
<span
className={
result.confidence >= 0.7
? 'text-green-400'
: result.confidence >= 0.4
? 'text-yellow-400'
: 'text-red-400'
}
>
{formatPercentage(result.confidence)}
</span>
</div>
</div>
);
}
interface TransformResultDisplayProps {
result: TransformResult;
}
/**
* Display transform result
*/
function TransformResultDisplay({ result }: TransformResultDisplayProps): React.ReactElement {
return (
<div className="space-y-4">
{/* Mode */}
<div className="flex items-center gap-2 text-sm">
<span className="text-ui-text-secondary">Transform Mode:</span>
<span className="text-ui-accent-primary capitalize">{result.mode}</span>
</div>
{/* Transformed content */}
<div>
<h3 className="text-sm font-medium text-ui-accent-secondary mb-2">Transformed Content</h3>
<p className="text-sm text-ui-text-primary whitespace-pre-wrap bg-ui-background/50 p-3 rounded-lg">
{result.transformed}
</p>
</div>
</div>
);
}
interface FindSimilarResultDisplayProps {
result: FindSimilarResult;
onNavigateToMemory?: (memoryId: string) => void;
}
/**
* Display find similar result
* Requirements: 9.3
*/
function FindSimilarResultDisplay({
result,
onNavigateToMemory,
}: FindSimilarResultDisplayProps): React.ReactElement {
return (
<div className="space-y-4">
{/* Stats */}
<div className="flex items-center gap-4 text-sm">
<span className="text-ui-text-secondary">
Found <span className="text-ui-accent-primary">{result.similarMemories.length}</span>{' '}
similar memories
</span>
{result.highlightedNodeIds.length > 0 && (
<span className="text-ui-text-secondary">
<span className="text-ui-accent-secondary">{result.highlightedNodeIds.length}</span>{' '}
visible in 3D view
</span>
)}
</div>
{/* Similar memories list */}
{result.similarMemories.length > 0 ? (
<div className="space-y-2">
<h3 className="text-sm font-medium text-ui-accent-secondary">Similar Memories</h3>
<div className="space-y-2 max-h-60 overflow-y-auto">
{result.similarMemories.map((memory) => (
<div
key={memory.id}
className="p-3 bg-ui-background/50 rounded-lg border border-ui-border hover:border-ui-accent-primary/50 transition-colors cursor-pointer"
onClick={() => onNavigateToMemory?.(memory.id)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
onNavigateToMemory?.(memory.id);
}
}}
>
<div className="flex justify-between items-start mb-1">
<span className="text-xs text-ui-accent-primary capitalize">
{memory.primarySector}
</span>
<span className="text-xs text-ui-text-secondary">
Score: {formatPercentage(memory.score.total)}
</span>
</div>
<p className="text-sm text-ui-text-primary line-clamp-2">{memory.content}</p>
</div>
))}
</div>
</div>
) : (
<p className="text-sm text-ui-text-secondary">No similar memories found.</p>
)}
</div>
);
}
interface AnalyzeResultDisplayProps {
result: AnalyzeResult;
}
/**
* Display analyze result (confidence + bias)
* Requirements: 9.4, 9.5
*/
function AnalyzeResultDisplay({ result }: AnalyzeResultDisplayProps): React.ReactElement {
const { confidence, bias } = result;
return (
<div className="space-y-6">
{/* Confidence Assessment */}
<div>
<h3 className="text-sm font-medium text-ui-accent-secondary mb-3">Confidence Assessment</h3>
{/* Overall confidence */}
<div className="flex items-center gap-2 mb-3">
<span className="text-ui-text-secondary text-sm">Overall:</span>
<span
className={`text-lg font-semibold ${confidence.overall >= 0.7 ? 'text-green-400' : confidence.overall >= 0.4 ? 'text-yellow-400' : 'text-red-400'}`}
>
{formatPercentage(confidence.overall)}
</span>
</div>
{/* Dimensions */}
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="flex justify-between">
<span className="text-ui-text-secondary">Evidence Quality:</span>
<span className="text-ui-text-primary">
{formatPercentage(confidence.dimensions.evidenceQuality)}
</span>
</div>
<div className="flex justify-between">
<span className="text-ui-text-secondary">Coherence:</span>
<span className="text-ui-text-primary">
{formatPercentage(confidence.dimensions.reasoningCoherence)}
</span>
</div>
<div className="flex justify-between">
<span className="text-ui-text-secondary">Completeness:</span>
<span className="text-ui-text-primary">
{formatPercentage(confidence.dimensions.completeness)}
</span>
</div>
<div className="flex justify-between">
<span className="text-ui-text-secondary">Uncertainty:</span>
<span className="text-ui-text-primary">
{formatPercentage(confidence.dimensions.uncertaintyLevel)}
</span>
</div>
<div className="flex justify-between col-span-2">
<span className="text-ui-text-secondary">Bias Freedom:</span>
<span className="text-ui-text-primary">
{formatPercentage(confidence.dimensions.biasFreedom)}
</span>
</div>
</div>
{/* Interpretation */}
{confidence.interpretation && (
<p className="mt-3 text-sm text-ui-text-primary bg-ui-background/50 p-2 rounded">
{confidence.interpretation}
</p>
)}
{/* Warnings */}
{confidence.warnings.length > 0 && (
<div className="mt-3">
<span className="text-xs text-yellow-400">Warnings:</span>
<ul className="mt-1 space-y-1">
{confidence.warnings.map((warning, index) => (
<li key={index} className="text-xs text-ui-text-secondary">
• {warning}
</li>
))}
</ul>
</div>
)}
</div>
{/* Bias Detection */}
<div>
<h3 className="text-sm font-medium text-ui-accent-secondary mb-3">Bias Detection</h3>
{/* Overall risk */}
<div className="flex items-center gap-2 mb-3">
<span className="text-ui-text-secondary text-sm">Overall Risk:</span>
<span
className={`text-sm font-semibold capitalize ${getRiskColorClass(bias.overallRisk)}`}
>
{bias.overallRisk}
</span>
</div>
{/* Detected biases */}
{bias.biases.length > 0 ? (
<div className="space-y-2">
{bias.biases.map((biasItem, index) => (
<div
key={index}
className={`p-3 rounded-lg border ${getSeverityColorClass(biasItem.severity)}`}
>
<div className="flex justify-between items-start mb-1">
<span className="text-sm font-medium capitalize">
{biasItem.type.replace('_', ' ')} Bias
</span>
<span className="text-xs">Severity: {formatPercentage(biasItem.severity)}</span>
</div>
{biasItem.evidence.length > 0 && (
<p className="text-xs opacity-80 mb-2">
Evidence: {biasItem.evidence.join(', ')}
</p>
)}
<p className="text-xs">
<span className="opacity-70">Correction: </span>
{biasItem.correctionStrategy}
</p>
</div>
))}
</div>
) : (
<p className="text-sm text-green-400">No significant biases detected.</p>
)}
{/* Recommendations */}
{bias.recommendations.length > 0 && (
<div className="mt-3">
<span className="text-xs text-ui-accent-primary">Recommendations:</span>
<ul className="mt-1 space-y-1">
{bias.recommendations.map((rec, index) => (
<li key={index} className="text-xs text-ui-text-secondary">
• {rec}
</li>
))}
</ul>
</div>
)}
</div>
</div>
);
}
// ============================================================================
// Main Component
// ============================================================================
/**
* Get modal title based on result type
*/
function getModalTitle(result: CognitiveToolResult): string {
switch (result.type) {
case 'summarize':
return 'Memory Summary';
case 'transform':
return 'Transformed Content';
case 'find-similar':
return 'Similar Memories';
case 'analyze':
return 'Analysis Results';
}
}
/**
* CognitiveResultModal - Modal for displaying cognitive tool results
*
* Requirements: 9.2, 9.3, 9.4, 9.5
*/
export function CognitiveResultModal({
result,
onClose,
onNavigateToMemory,
className = '',
}: CognitiveResultModalProps): React.ReactElement {
const title = getModalTitle(result);
return (
<ModalOverlay onClose={onClose}>
<ModalContent title={title} onClose={onClose}>
<div className={className}>
{result.type === 'summarize' && <SummarizeResultDisplay result={result} />}
{result.type === 'transform' && <TransformResultDisplay result={result} />}
{result.type === 'find-similar' && (
<FindSimilarResultDisplay
result={result}
{...(onNavigateToMemory !== undefined ? { onNavigateToMemory } : {})}
/>
)}
{result.type === 'analyze' && <AnalyzeResultDisplay result={result} />}
</div>
</ModalContent>
</ModalOverlay>
);
}
export default CognitiveResultModal;