Emissary Choose Component
Overview
The Emissary Choose Component (src/components/EmissaryChoose.tsx) provides an interactive template selection interface for creating new AI assistants. It displays available templates in a responsive carousel layout, allowing users to choose from pre-defined assistant templates or create custom ones.
Key Features
Template Selection Interface
- Carousel layout: Responsive template browsing with navigation controls
- Template preview: Shows avatar, name, description, and message preview
- Selection state: Visual feedback for currently selected template
- Custom option: Dedicated “New Assistant” option for custom creation
Responsive Design
- Mobile-first: Optimized for touch interfaces
- Breakpoint adaptation: Different layouts for mobile, tablet, and desktop
- Flexible cards: Consistent card sizing across screen sizes
- Touch navigation: Swipe gestures and navigation buttons
Component Structure
Props Interface
interface EmissaryChooseProps {
templates: Template[];
selectedTemplate: string;
onSelect: (templateId: string) => void;
}
Template Data Structure
interface Template {
templateId: string;
name: string;
avatar: string;
defaultMessage?: string;
isTemplate: boolean;
}
Core Functionality
Template Display Logic
const truncateText = (text: string, limit: number) => {
if (!text) return '';
return text.length > limit ? `${text.substring(0, limit)}...` : text;
};
Carousel Configuration
<Carousel opts={{loop: true}} className="w-full md:max-w-xl md:mx-auto">
<CarouselContent className="-ml-1">
{templates.map((template) => (
<CarouselItem key={template.templateId} className="pl-1 basis-full sm:basis-1/2 lg:basis-1/3">
{/* Template card content */}
</CarouselItem>
))}
{/* Custom "New Assistant" option */}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
Template Card Design
Standard Template Card
<Card className={`border-2 h-[400px] max-w-[300px] mx-auto ${
selectedTemplate === template.templateId ? 'border-blue-500' : 'border-transparent'
}`}>
<CardContent className="flex flex-col items-center p-4 h-full">
{/* Avatar section */}
<div className="w-20 h-20 rounded-full overflow-hidden mb-4 border-2 border-gray-200 shrink-0">
<img
src={template.avatar}
alt={template.name}
className="w-full h-full object-cover"
onError={(e) => {
const img = e.target as HTMLImageElement;
img.src = `https://ui-avatars.com/api/?name=${template.name}&background=random`;
}}
/>
</div>
{/* Content section */}
<h3 className="font-semibold text-center mb-2 line-clamp-1">{template.name}</h3>
<p className="text-sm text-gray-500 text-center mb-4">
{template.isTemplate ? 'Template' : 'Custom'}
</p>
<p className="text-sm text-gray-600 text-center mb-4 overflow-y-auto flex-1">
{truncateText(template.defaultMessage || '', 200)}
</p>
{/* Selection button */}
<Button
variant={selectedTemplate === template.templateId ? "default" : "outline"}
className="w-full mt-auto"
onClick={() => onSelect(template.templateId)}
>
{selectedTemplate === template.templateId ? (
<span className="flex items-center gap-2">
<Check className="w-4 h-4" />
Selected
</span>
) : (
'Select'
)}
</Button>
</CardContent>
</Card>
Custom Assistant Card
<Card className={`border-2 h-[400px] max-w-[300px] mx-auto ${
selectedTemplate === 'custom' ? 'border-blue-500' : 'border-transparent'
}`}>
<CardContent className="flex flex-col items-center p-4 h-full">
{/* Plus icon avatar */}
<div className="w-20 h-20 rounded-full overflow-hidden mb-4 border-2 border-gray-200 flex items-center justify-center bg-gray-100 shrink-0">
<span className="text-3xl">+</span>
</div>
<h3 className="font-semibold text-center mb-2">New Assistant</h3>
<p className="text-sm text-gray-500 text-center mb-4">
Create your own
</p>
<p className="text-sm text-gray-600 text-center mb-4 flex-1">
Create a custom assistant with your own instructions and personality.
</p>
<Button
variant={selectedTemplate === 'custom' ? "default" : "outline"}
className="w-full mt-auto"
onClick={() => onSelect('custom')}
>
{selectedTemplate === 'custom' ? (
<span className="flex items-center gap-2">
<Check className="w-4 h-4" />
Selected
</span>
) : (
'Select'
)}
</Button>
</CardContent>
</Card>
Visual Design Features
Selection States
- Selected: Blue border (
border-blue-500) with filled button - Unselected: Transparent border with outline button
- Selection feedback: Check icon and “Selected” text for active choice
Avatar Handling
- Primary avatar: Displays template avatar image
- Fallback avatar: Auto-generated avatar using UI-avatars service
- Error handling: Graceful fallback when avatar image fails to load
- Consistent sizing: Fixed 20x20 (80px) avatar size across all cards
Content Layout
- Fixed height: All cards maintain 400px height for consistency
- Flexible content: Template description uses available space with overflow scroll
- Bottom alignment: Selection button always positioned at card bottom
- Responsive width: Maximum 300px width with responsive breakpoints
Responsive Breakpoints
Carousel Item Sizing
className="pl-1 basis-full sm:basis-1/2 lg:basis-1/3"
Breakpoint Behavior:
- Mobile (< 640px): One card per view (
basis-full) - Tablet (640px - 1024px): Two cards per view (
sm:basis-1/2) - Desktop (> 1024px): Three cards per view (
lg:basis-1/3)
Container Constraints
className="w-full md:max-w-xl md:mx-auto"
- Mobile: Full width container
- Desktop: Maximum 576px width, centered
Error Handling
Image Loading Errors
onError={(e) => {
const img = e.target as HTMLImageElement;
img.src = `https://ui-avatars.com/api/?name=${template.name}&background=random`;
}}
Missing Content Handling
- Empty descriptions: Handled gracefully with empty string fallback
- Missing names: Component assumes name is always present
- Invalid template IDs: Parent component responsibility to validate
Integration Examples
Basic Usage
import EmissaryChoose from '@/components/EmissaryChoose';
function AssistantCreator() {
const [selectedTemplate, setSelectedTemplate] = useState('');
const [templates, setTemplates] = useState([]);
return (
<div className="assistant-creation-flow">
<h2>Choose a Template</h2>
<EmissaryChoose
templates={templates}
selectedTemplate={selectedTemplate}
onSelect={setSelectedTemplate}
/>
{selectedTemplate && (
<Button onClick={() => proceedWithTemplate(selectedTemplate)}>
Continue
</Button>
)}
</div>
);
}
With Template Loading
const [templates, setTemplates] = useState<Template[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadTemplates = async () => {
try {
const templateData = await FirebaseService.getTemplates();
setTemplates(templateData);
} catch (error) {
console.error('Failed to load templates:', error);
} finally {
setLoading(false);
}
};
loadTemplates();
}, []);
if (loading) {
return <LoadingSpinner />;
}
return (
<EmissaryChoose
templates={templates}
selectedTemplate={selectedTemplate}
onSelect={(templateId) => {
setSelectedTemplate(templateId);
// Optional: analytics tracking
analytics.track('template_selected', { templateId });
}}
/>
);
Accessibility Features
Keyboard Navigation
- Carousel controls: Arrow buttons are keyboard accessible
- Card selection: Cards can be focused and activated with keyboard
- Tab order: Logical tab sequence through all interactive elements
Screen Reader Support
- Alt text: Descriptive alt text for template avatars
- Button labels: Clear labeling for selection buttons
- Semantic structure: Proper heading hierarchy and card structure
Visual Accessibility
- High contrast: Clear visual distinction between selected and unselected states
- Focus indicators: Standard focus outlines for keyboard navigation
- Text sizing: Readable text sizes across all content
Performance Considerations
Image Optimization
- Avatar loading: Lazy loading handled by browser
- Fallback generation: External UI-avatars service for failed images
- Caching: Browser caches avatar images automatically
Rendering Efficiency
- Fixed layouts: Consistent card dimensions prevent layout shifts
- Minimal re-renders: Component only re-renders when props change
- Efficient selection: State updates only trigger button re-renders
Dependencies
UI Components
- Carousel: Custom carousel component with navigation
- Card: Standard card component for consistent styling
- Button: Themed button component with variants
- Icons: Check icon from Lucide React
External Services
- UI-avatars: Fallback avatar generation service
- Firebase: Template data source (via parent component)
Styling and Theme
CSS Classes
- Layout: Flexbox and grid utilities for responsive design
- Spacing: Consistent margin and padding using Tailwind scale
- Colors: Theme-aware colors for borders and text
- Typography: Responsive text sizing and weight hierarchy
Customization Points
- Card dimensions: Easily adjustable height and width constraints
- Breakpoints: Configurable responsive behavior
- Color scheme: Theme-aware color variables
- Animation: Smooth transitions for selection states
Related Components
- EmissaryList - List view of assistants
- AssistantDetails - Assistant creation form
- AvatarSelector - Avatar selection interface
Future Enhancements
Planned Features
- Template preview: Expanded preview with sample conversations
- Category filtering: Filter templates by category or use case
- Search functionality: Search templates by name or description
- Favorites: Mark frequently used templates as favorites
UX Improvements
- Template ratings: Community ratings for template quality
- Usage statistics: Show popular templates based on usage
- Template sharing: Share custom templates with other users
- Advanced preview: Interactive template demonstration
Troubleshooting
Common Issues
Templates not displaying
// Check template data structure
// Verify templateId uniqueness
// Ensure avatar URLs are accessible
Selection not working
// Verify onSelect callback is provided
// Check selectedTemplate prop value
// Ensure templateId matches expected format
Carousel navigation issues
// Check carousel component props
// Verify navigation button visibility
// Test on different screen sizes