Emissary List Minimal Component
Overview
The Emissary List Minimal Component (src/components/EmissaryListMinimal.tsx) provides a compact sidebar interface for displaying recent AI assistant conversations. It’s optimized for sidebar integration with minimal space usage while maintaining full functionality for navigation and conversation access.
Key Features
Sidebar Integration
- Compact design: Optimized for narrow sidebar spaces
- SidebarMenu integration: Built using the application’s sidebar component system
- Responsive behavior: Adapts to different sidebar widths
- Minimal visual footprint: Clean, space-efficient layout
Recent Conversations
- Last accessed sorting: Shows assistants ordered by recent activity
- Message preview: Displays latest message content for each assistant
- Relative timestamps: Human-readable time indicators
- Expandable list: Collapsible interface for managing large lists
Smart Features
- Firebase integration: Real-time message fetching and display
- Loading states: Graceful loading indicators
- Empty states: Helpful messaging when no conversations exist
- Tooltip support: Enhanced information on hover
Component Structure
Props Interface
interface EmissaryListMinimalProps {
userId?: string;
maxItems?: number;
isCollapsible?: boolean;
}
Configuration Options
- userId: User identifier for fetching personal assistants
- maxItems: Maximum number of assistants to display initially (default: 5)
- isCollapsible: Whether to show expand/collapse functionality (default: true)
Core Functionality
Assistant Loading and Sorting
useEffect(() => {
const loadRecentAssistants = async () => {
if (!userId) {
setIsLoading(false);
return;
}
setIsLoading(true);
try {
// Get user's assistants
const userAssistants = await FirebaseService.getUserAssistants(userId);
// Sort by last accessed date, most recent first
const sortedAssistants = userAssistants
.sort((a, b) => {
const aTime = a.lastAccessedAt ? new Date(a.lastAccessedAt).getTime() : 0;
const bTime = b.lastAccessedAt ? new Date(b.lastAccessedAt).getTime() : 0;
return bTime - aTime;
})
.map(assistant => ({
// Transform to AssistantDisplay format
assistantId: assistant.assistantId,
name: assistant.name,
avatar: assistant.avatar,
shareUrl: `/assistant/${assistant.assistantId}`,
lastAccessedAt: assistant.lastAccessedAt ? new Date(assistant.lastAccessedAt) : undefined,
// ... other properties
}));
setAssistants(sortedAssistants);
// Fetch last messages for each assistant
await fetchLastMessages(sortedAssistants);
} catch (error) {
console.error('Error loading recent assistants:', error);
setAssistants([]);
} finally {
setIsLoading(false);
}
};
loadRecentAssistants();
}, [userId]);
Message Fetching
// Fetch last message for each assistant
const messagePromises = sortedAssistants.map(async (assistant) => {
try {
const messages = await FirebaseService.getMessages(assistant.assistantId);
if (messages.length > 0) {
// Get the last message (most recent)
const lastMessage = messages[messages.length - 1];
return { assistantId: assistant.assistantId, message: lastMessage };
}
return null;
} catch (error) {
console.error(`Error fetching messages for assistant ${assistant.assistantId}:`, error);
return null;
}
});
const messageResults = await Promise.all(messagePromises);
const messageMap: Record<string, Message> = {};
messageResults.forEach(result => {
if (result) {
messageMap[result.assistantId] = result.message;
}
});
setLastMessages(messageMap);
UI Implementation
Sidebar Menu Structure
return (
<TooltipProvider>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{displayedAssistants.map((assistant) => {
const lastMessage = lastMessages[assistant.assistantId];
const displayContent = lastMessage
? truncateMessage(lastMessage.content)
: 'No messages yet';
const tooltipContent = lastMessage?.content
? truncateMessage(lastMessage.content, 200)
: 'Start a conversation';
return (
<SidebarMenuItem key={assistant.assistantId} className="my-0.5">
<Tooltip>
<TooltipTrigger asChild>
<SidebarMenuButton asChild className="h-auto py-2">
<Link href={assistant.shareUrl || ''} className="flex items-center gap-2 w-full">
{/* Assistant content */}
</Link>
</SidebarMenuButton>
</TooltipTrigger>
<TooltipContent side="right" className="max-w-xs">
{/* Tooltip content */}
</TooltipContent>
</Tooltip>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</TooltipProvider>
);
Assistant Item Layout
<Link href={assistant.shareUrl || ''} className="flex items-center gap-2 w-full">
<Avatar className="h-6 w-6 shrink-0">
<AvatarImage src={assistant.avatar} alt={assistant.name} />
<AvatarFallback>{assistant.name[0]}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="text-xs text-muted-foreground truncate">
{displayContent}
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="text-xs text-muted-foreground">
<RelativeTime date={assistant.lastAccessedAt} showTooltip={false} />
</span>
<MessageCircle className="h-3 w-3 text-muted-foreground" />
</div>
</Link>
Content Management
Message Truncation
const truncateMessage = (content: string, maxLength: number = 30) => {
if (content.length <= maxLength) return content;
return content.substring(0, maxLength).trim() + '...';
};
Expand/Collapse Functionality
const displayedAssistants = isExpanded ? assistants : assistants.slice(0, maxItems);
const hasMore = assistants.length > maxItems;
{isCollapsible && hasMore && (
<Button
variant="ghost"
size="sm"
className="w-full mt-2 justify-center"
onClick={() => setIsExpanded(!isExpanded)}
>
{isExpanded ? (
<>
<ChevronUp className="h-4 w-4 mr-1" />
Show Less
</>
) : (
<>
<ChevronDown className="h-4 w-4 mr-1" />
Show {assistants.length - maxItems} More
</>
)}
</Button>
)}
Tooltip Enhancement
Rich Tooltip Content
<TooltipContent side="right" className="max-w-xs">
<div className="space-y-1">
<div className="flex items-center gap-2">
<Avatar className="h-6 w-6 shrink-0">
<AvatarImage src={assistant.avatar} alt={assistant.name} />
<AvatarFallback>{assistant.name[0]}</AvatarFallback>
</Avatar>
<p className="font-medium">{assistant.name}</p>
</div>
<p className="text-sm text-muted-foreground">{tooltipContent}</p>
</div>
</TooltipContent>
Tooltip Features:
- Assistant name: Shows full assistant name
- Avatar: Displays assistant avatar for visual recognition
- Extended preview: Shows longer message preview (200 characters)
- Contextual content: Different content for conversations vs. new assistants
State Management
Loading States
if (isLoading) {
return (
<SidebarGroup>
<SidebarGroupContent>
<div className="p-4 text-center text-sm text-muted-foreground">
Loading...
</div>
</SidebarGroupContent>
</SidebarGroup>
);
}
Empty States
if (assistants.length === 0) {
return (
<SidebarGroup>
<SidebarGroupContent>
<div className="p-4 text-center text-sm text-muted-foreground">
No conversations yet
</div>
</SidebarGroupContent>
</SidebarGroup>
);
}
Error Handling
try {
const userAssistants = await FirebaseService.getUserAssistants(userId);
// Process assistants
} catch (error) {
console.error('Error loading recent assistants:', error);
toast({
title: "Error Loading Assistants",
description: "Failed to load recent assistants",
variant: "destructive"
});
setAssistants([]);
}
Integration Examples
Sidebar Integration
import { EmissaryListMinimal } from '@/components/EmissaryListMinimal';
function AppSidebar({ currentUser }) {
return (
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Recent Conversations</SidebarGroupLabel>
<EmissaryListMinimal
userId={currentUser?.uid}
maxItems={5}
isCollapsible={true}
/>
</SidebarGroup>
{/* Other sidebar content */}
</SidebarContent>
</Sidebar>
);
}
Custom Configuration
// Show more items initially
<EmissaryListMinimal
userId={userId}
maxItems={10}
isCollapsible={true}
/>
// Fixed list without expansion
<EmissaryListMinimal
userId={userId}
maxItems={3}
isCollapsible={false}
/>
// Guest mode (no user ID)
<EmissaryListMinimal
userId={undefined}
maxItems={5}
/>
Performance Optimizations
Efficient Data Loading
// Only fetch when user ID is available
if (!userId) {
setIsLoading(false);
return;
}
// Add delay to ensure Firebase initialization
await new Promise(resolve => setTimeout(resolve, 100));
Parallel Message Fetching
// Fetch messages for all assistants in parallel
const messagePromises = sortedAssistants.map(async (assistant) => {
try {
const messages = await FirebaseService.getMessages(assistant.assistantId);
return messages.length > 0 ? { assistantId: assistant.assistantId, message: messages[messages.length - 1] } : null;
} catch (error) {
return null;
}
});
const messageResults = await Promise.all(messagePromises);
Memory Efficiency
- Lazy loading: Only loads data when user ID is provided
- Selective rendering: Only renders displayed items
- Cleanup: Proper cleanup of async operations
Accessibility Features
Keyboard Navigation
- Tab support: Full keyboard navigation through items
- Link activation: Standard link activation with Enter key
- Focus management: Proper focus indicators
Screen Reader Support
- Semantic HTML: Proper use of navigation and list elements
- Alt text: Descriptive alt text for avatars
- ARIA labels: Appropriate labeling for interactive elements
Visual Accessibility
- High contrast: Clear visual hierarchy and contrast
- Icon support: Meaningful icons with text labels
- Responsive text: Scalable text sizing
Related Components
- EmissaryListCompact - Card-based assistant listing
- EmissaryList - Full-featured assistant list
- RelativeTime - Time display component
- Sidebar Components - Sidebar system integration
Future Enhancements
Planned Features
- Drag and drop: Reorder conversations by importance
- Context menu: Right-click actions for conversations
- Search integration: Quick search through conversations
- Keyboard shortcuts: Hotkeys for quick navigation
Performance Improvements
- Virtual scrolling: Handle large numbers of conversations
- Caching: Cache recent conversation data
- Incremental loading: Load conversations on demand
- Real-time updates: Live updates when new messages arrive
Troubleshooting
Common Issues
Conversations not loading
// Check userId is valid
// Verify Firebase authentication
// Check network connectivity
// Review console for error messages
Messages not displaying
// Verify Firebase permissions
// Check message data structure
// Ensure proper async handling
// Test with different assistants
Tooltip not showing
// Verify TooltipProvider is wrapping component
// Check tooltip positioning
// Ensure proper hover events
// Test on different screen sizes