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 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

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

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