AVATAR_SELECTOR_COMPONENT.md
Overview
The AvatarSelector component provides a comprehensive interface for selecting and managing avatar images with support for both predefined avatar galleries and custom URL inputs. It features API-driven avatar loading, image validation, and fallback handling.
Purpose
- Avatar Management: Select from gallery or custom URLs for user/assistant avatars
- Image Validation: Handle image loading errors with fallback mechanisms
- Gallery Integration: API-driven avatar gallery with grid-based selection
- Custom Images: Support for external image URLs with validation
Key Features
Gallery Selection
- API Integration: Fetches available avatars from
/api/avatarsendpoint - Grid Layout: Responsive 4-column grid for avatar selection
- Visual Selection: Clear selection indicators with check marks
- Image Validation: Error handling for broken/missing images
Custom URL Support
- URL Input: Text input for custom avatar URLs
- Real-time Validation: Immediate validation and preview
- Fallback Handling: Automatic fallback to default avatar on errors
- URL State Management: Tracks custom vs gallery avatar usage
Component Interface
interface AvatarSelectorProps {
currentAvatar: string; // Current avatar URL
onAvatarChange: (avatar: string) => void; // Avatar change callback
className?: string; // Additional CSS classes
}
Core Functionality
1. Avatar Gallery Management
// API-driven avatar loading
useEffect(() => {
const fetchAvatars = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch('/api/avatars');
if (!response.ok) {
throw new Error('Failed to fetch avatars');
}
const data = await response.json();
setAvailableAvatars(data.avatars);
} catch (err) {
console.error('Error fetching avatars:', err);
setError('Failed to load avatars');
} finally {
setLoading(false);
}
};
fetchAvatars();
}, []);
// Gallery selection handler
const handleAvatarSelect = (avatarFilename: string) => {
const fullPath = `/images/avatars/${avatarFilename}`;
onAvatarChange(fullPath);
setCustomUrl(fullPath);
setUseCustomUrl(false);
setOpen(false);
};
2. Avatar Grid Rendering
// Gallery grid with selection indicators
<ScrollArea className="h-[300px] rounded-md border p-4">
<div className="grid grid-cols-4 gap-4">
{availableAvatars.map((avatarFile) => {
const avatarPath = `/images/avatars/${avatarFile}`;
const isSelected = currentAvatar === avatarPath;
return (
<button
key={avatarFile}
onClick={() => handleAvatarSelect(avatarFile)}
className={cn(
"relative aspect-square rounded-lg overflow-hidden border-2 transition-all",
isSelected
? "border-primary ring-2 ring-primary ring-offset-2"
: "border-transparent hover:border-muted-foreground/50"
)}
>
<img
src={avatarPath}
alt={avatarFile}
className="w-full h-full object-cover"
onError={(e) => {
const img = e.target as HTMLImageElement;
img.style.display = 'none';
}}
/>
{isSelected && (
<div className="absolute inset-0 bg-primary/20 flex items-center justify-center">
<Check className="h-6 w-6 text-primary" />
</div>
)}
<div className="absolute bottom-0 left-0 right-0 bg-black/70 text-white text-xs p-1 truncate">
{avatarFile.replace(/\.(jpg|jpeg|png|svg)$/i, '')}
</div>
</button>
);
})}
</div>
</ScrollArea>
3. Custom URL Handling
// Custom URL input and validation
const handleCustomUrlSubmit = () => {
if (customUrl && customUrl.trim()) {
onAvatarChange(customUrl.trim());
setUseCustomUrl(true);
setOpen(false);
}
};
// Custom URL interface
<div className="space-y-2">
<h3 className="text-sm font-medium">Or use a custom URL:</h3>
<div className="flex gap-2">
<input
type="text"
value={customUrl}
onChange={(e) => setCustomUrl(e.target.value)}
placeholder="https://example.com/avatar.png"
className="flex-1 px-3 py-2 border rounded-md text-sm"
/>
<Button
onClick={handleCustomUrlSubmit}
disabled={!customUrl || !customUrl.trim()}
size="sm"
>
Use URL
</Button>
</div>
</div>
4. Image Error Handling
// Avatar display with fallback
<img
src={currentAvatar || '/images/avatars/aitana_help.jpeg'}
alt="Current avatar"
className="w-full h-full object-cover"
onError={(e) => {
const img = e.target as HTMLImageElement;
img.src = '/images/avatars/aitana_help.jpeg';
}}
/>
// Gallery image error handling
<img
src={avatarPath}
alt={avatarFile}
className="w-full h-full object-cover"
onError={(e) => {
const img = e.target as HTMLImageElement;
img.style.display = 'none';
}}
/>
State Management
// Component state variables
const [open, setOpen] = useState(false); // Dialog open state
const [customUrl, setCustomUrl] = useState(currentAvatar); // Custom URL input
const [useCustomUrl, setUseCustomUrl] = useState(!currentAvatar.includes('/images/avatars/')); // URL vs gallery
const [availableAvatars, setAvailableAvatars] = useState<string[]>([]); // Gallery avatars
const [loading, setLoading] = useState(true); // Loading state
const [error, setError] = useState<string | null>(null); // Error state
Integration Examples
Basic Usage
function UserProfile() {
const [avatar, setAvatar] = useState('/images/avatars/default.jpg');
return (
<div className="profile-form">
<AvatarSelector
currentAvatar={avatar}
onAvatarChange={setAvatar}
/>
</div>
);
}
Assistant Creation
function AssistantForm() {
const [assistantData, setAssistantData] = useState({
name: '',
avatar: '/images/avatars/aitana_help.jpeg',
description: ''
});
return (
<form>
<AvatarSelector
currentAvatar={assistantData.avatar}
onAvatarChange={(avatar) =>
setAssistantData(prev => ({ ...prev, avatar }))
}
className="mb-4"
/>
</form>
);
}
API Integration
Expected Endpoint Response
// GET /api/avatars
{
"avatars": [
"aitana_help.jpeg",
"assistant1.png",
"user_default.jpg",
// ... more avatar filenames
]
}
Error Handling
// Loading state
{loading && (
<div className="flex items-center justify-center h-[300px] border rounded-md">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
<p className="text-sm text-muted-foreground">Loading avatars...</p>
</div>
)}
// Error state
{error && (
<div className="flex items-center justify-center h-[300px] border rounded-md">
<div className="text-center">
<p className="text-sm text-destructive mb-2">{error}</p>
<p className="text-xs text-muted-foreground">Please try again later</p>
</div>
</div>
)}
Features
Visual Design
- Current Avatar Display: Circular preview with border
- Selection Indicators: Primary color rings and check marks
- Hover Effects: Subtle border changes on gallery items
- Grid Layout: Responsive 4-column layout for avatars
- Loading States: Spinner animation during API calls
Accessibility
- Keyboard Navigation: Full keyboard support for selection
- Screen Reader Support: Proper alt attributes and labels
- Focus Management: Logical tab order through interface
- Error Announcements: Screen reader accessible error messages
Performance
- Lazy Loading: Images loaded as needed in gallery
- Error Boundaries: Graceful handling of image failures
- API Optimization: Single fetch for avatar gallery
- State Optimization: Minimal re-renders through proper state management