AI_Agent_Final / web /src /components /SmartReview.tsx
SarahXia0405's picture
Upload 73 files
0ef5c60 verified
import React, { useState } from 'react';
import { Brain, AlertTriangle, AlertCircle, CheckCircle, ChevronDown, ChevronRight, Info } from 'lucide-react';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
interface ReviewItem {
id: string;
title: string;
schedule: string;
status: 'urgent' | 'review' | 'stable';
weight: number;
lastReviewed: string;
memoryRetention: number;
previousQuestion: string;
}
interface SmartReviewProps {
onReviewTopic?: (item: ReviewItem) => void;
onReviewAll?: () => void;
}
export function SmartReview({ onReviewTopic, onReviewAll }: SmartReviewProps = {}) {
// Initialize with the first item of W-4 (red zone) expanded by default
const [expandedItems, setExpandedItems] = useState<string[]>(['w4-1']);
const [selectedCategory, setSelectedCategory] = useState<string>('W-4'); // Default to red zone
const reviewData = [
{
label: 'W-4',
percentage: 35,
color: 'bg-red-500',
textColor: 'text-red-500',
icon: AlertTriangle,
description: 'Higher forgetting risk',
items: [
{
id: 'w4-1',
title: 'Main Concept of Lab 3',
schedule: 'T+7',
status: 'urgent' as const,
weight: 35,
lastReviewed: '7 days ago',
memoryRetention: 25,
previousQuestion: 'I\'ve read the instructions for Lab 3, but what is the main concept we\'re supposed to be learning here?'
}
]
},
{
label: 'W-2',
percentage: 20,
color: 'bg-orange-500',
textColor: 'text-orange-500',
icon: AlertCircle,
description: 'Medium forgetting risk',
items: [
{
id: 'w2-1',
title: 'Effective Prompt Engineering',
schedule: 'T+14',
status: 'review' as const,
weight: 20,
lastReviewed: '3 days ago',
memoryRetention: 60,
previousQuestion: 'I understand what prompt engineering is, but what specifically makes a prompt effective versus ineffective?'
}
]
},
{
label: 'W-1',
percentage: 12,
color: 'bg-green-500',
textColor: 'text-green-500',
icon: CheckCircle,
description: 'Recently learned',
items: [
{
id: 'w1-1',
title: 'Objective LLM Evaluation',
schedule: 'T+7',
status: 'stable' as const,
weight: 12,
lastReviewed: '1 day ago',
memoryRetention: 90,
previousQuestion: 'How can we objectively evaluate an LLM\'s performance when the output quality seems so subjective?'
}
]
},
];
const totalPercentage = reviewData.reduce((sum, item) => sum + item.percentage, 0);
const selectedData = reviewData.find(item => item.label === selectedCategory);
const toggleItem = (itemId: string) => {
setExpandedItems(prev =>
prev.includes(itemId)
? prev.filter(id => id !== itemId)
: [...prev, itemId]
);
};
// When category changes, automatically expand the first item of that category
const handleCategoryChange = (categoryLabel: string) => {
setSelectedCategory(categoryLabel);
const categoryData = reviewData.find(item => item.label === categoryLabel);
if (categoryData && categoryData.items.length > 0) {
// Expand only the first item of the selected category
setExpandedItems([categoryData.items[0].id]);
}
};
const getStatusBadge = (status: 'urgent' | 'review' | 'stable') => {
const configs = {
urgent: { label: 'URGENT', className: 'bg-red-500 text-white hover:bg-red-600' },
review: { label: 'DUE', className: 'bg-orange-500 text-white hover:bg-orange-600' },
stable: { label: 'STABLE', className: 'bg-green-500 text-white hover:bg-green-600' },
};
return configs[status];
};
const getButtonColorClass = (colorClass: string) => {
// Extract color from bg-red-500, bg-orange-500, bg-green-500
if (colorClass.includes('red')) {
return 'bg-red-500 hover:bg-red-600';
} else if (colorClass.includes('orange')) {
return 'bg-orange-500 hover:bg-orange-600';
} else if (colorClass.includes('green')) {
return 'bg-green-500 hover:bg-green-600';
}
return 'bg-red-500 hover:bg-red-600'; // default
};
return (
<div className="space-y-4">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Brain className="h-5 w-5 text-red-500" />
<h3>Current Review Distribution</h3>
<Tooltip>
<TooltipTrigger asChild>
<button className="text-muted-foreground hover:text-foreground transition-colors">
<Info className="h-3 w-3" />
</button>
</TooltipTrigger>
<TooltipContent side="right" className="p-2" style={{ maxWidth: '170px', whiteSpace: 'normal', wordBreak: 'break-word' }}>
<p className="text-[10px] leading-relaxed text-left" style={{ whiteSpace: 'normal' }}>
Based on the forgetting curve, Clare has selected topics you might be forgetting from your learning history and interaction patterns. Higher weights indicate higher forgetting risk.
</p>
</TooltipContent>
</Tooltip>
</div>
</div>
{/* Combined Progress Bar - Clickable */}
<div className="space-y-2">
<div className="flex items-center gap-3">
{reviewData.map((item) => (
<button
key={item.label}
className={`flex flex-col gap-1.5 pt-2 px-1 pb-0 rounded-lg transition-all duration-200 hover:brightness-110 focus:outline-none cursor-pointer ${
selectedCategory === item.label ? 'bg-muted/80' : 'bg-transparent hover:bg-muted/40'
}`}
onClick={() => handleCategoryChange(item.label)}
title={`Click to view ${item.label} items`}
style={{ flex: item.percentage }}
>
<div className="flex items-center gap-1 justify-center whitespace-nowrap">
<span className="text-[10px]">{item.label}:</span>
<span className={`text-[10px] font-medium ${item.textColor}`}>{item.percentage}%</span>
</div>
<div className={`h-2 ${item.color} rounded-full mb-2`} />
</button>
))}
</div>
</div>
{/* Selected Category Expandable Items */}
{selectedData && (
<div className="space-y-2">
{/* Expandable Review Items */}
<div className="space-y-2">
{selectedData.items.map((reviewItem) => (
<Collapsible
key={reviewItem.id}
open={expandedItems.includes(reviewItem.id)}
onOpenChange={() => toggleItem(reviewItem.id)}
>
<CollapsibleTrigger asChild>
<Button
variant="ghost"
className="w-full justify-between text-left h-auto p-2 hover:bg-muted/50"
>
<span className="text-sm">Review: {reviewItem.title}</span>
{expandedItems.includes(reviewItem.id) ? (
<ChevronDown className="h-4 w-4 flex-shrink-0" />
) : (
<ChevronRight className="h-4 w-4 flex-shrink-0" />
)}
</Button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="p-3 pl-4">
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className="text-xs">
{reviewItem.schedule}
</Badge>
<Badge className={`text-xs ${getStatusBadge(reviewItem.status).className}`}>
{getStatusBadge(reviewItem.status).label}
</Badge>
<span className="text-xs text-muted-foreground">
Weight: {reviewItem.weight}% | Last: {reviewItem.lastReviewed}
</span>
</div>
{/* Empty line for spacing */}
<div className="h-4"></div>
<div className="space-y-2">
{/* Titles row - aligned horizontally */}
<div className="flex gap-4 items-baseline">
<div className="flex-shrink-0">
<span className="text-xs text-muted-foreground leading-none">Memory Retention</span>
</div>
<div className="flex-1 min-w-0 pl-2">
<span className="text-xs text-muted-foreground leading-none">You previously asked:</span>
</div>
</div>
{/* Content row */}
<div className="flex gap-4 items-start">
{/* Left: Memory Retention Pie Chart */}
<div className="flex-shrink-0 relative w-24 h-24">
<svg className="w-full h-full" viewBox="0 0 100 100" style={{ transform: 'rotate(-90deg)' }}>
{/* Background circle */}
<circle
cx="50"
cy="50"
r="45"
fill="none"
stroke="currentColor"
strokeWidth="8"
className="text-muted"
/>
{/* Filled arc */}
<circle
cx="50"
cy="50"
r="45"
fill="none"
stroke="currentColor"
strokeWidth="8"
strokeDasharray={`${2 * Math.PI * 45}`}
strokeDashoffset={`${2 * Math.PI * 45 * (1 - reviewItem.memoryRetention / 100)}`}
strokeLinecap="round"
className={selectedData.color.replace('bg-', 'text-')}
/>
</svg>
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<span className={`text-sm font-medium ${selectedData.textColor}`}>
{reviewItem.memoryRetention}%
</span>
</div>
</div>
{/* Right: Previous Question and Review Button */}
<div className="flex-1 h-24 flex flex-col justify-between min-w-0 pl-2">
<div className="flex-shrink-0">
<p className="text-xs bg-muted/50 p-2 rounded italic m-0">
"{reviewItem.previousQuestion}"
</p>
</div>
<div className="flex-shrink-0 flex gap-2">
<Button
className={`flex-1 ${getButtonColorClass(selectedData.color)} text-white`}
size="sm"
onClick={() => onReviewTopic?.(reviewItem)}
>
Review this topic
</Button>
<Button
variant="outline"
className="flex-1"
size="sm"
onClick={() => onReviewAll?.()}
>
Review All
</Button>
</div>
</div>
</div>
</div>
</div>
</CollapsibleContent>
</Collapsible>
))}
</div>
</div>
)}
</div>
);
}