Emissary Component System
Overview
The Emissary Component System is the core frontend architecture for AI assistant interactions in the Aitana platform. It provides a sophisticated, context-driven interface that handles real-time streaming, tool integration, authentication, and message persistence.
Architecture Overview
The Emissary system is built on a multi-layered context architecture designed for performance, maintainability, and extensibility. It consists of several specialized components working together to provide a seamless AI assistant experience.
Core Components
Primary Components
EmissaryOptimized.tsx- Main optimized assistant interfaceEmissaryChoose.tsx- Assistant selection carouselEmissaryList.tsx- Full-featured assistant management interfaceEmissaryListCompact.tsx- Compact variant for limited spaceEmissaryListMinimal.tsx- Minimal variant for simple displaysEmissaryLoading.tsx- Loading states and transitionsEmissaryEmptyState.tsx- Empty states when no assistants available
EmissaryOptimized - Primary Component
Component Architecture
The EmissaryOptimized component is the central hub for AI assistant interactions, designed with performance optimization and clean separation of concerns.
Key Features:
- Optimized Context Integration: Uses multiple specialized contexts
- Real-time Streaming: Handles streaming AI responses
- Tool Integration: Manages tool confirmations and configurations
- Authentication Flow: Handles login/logout with Firebase
- Message Persistence: Integrates with Firestore for chat history
- Artifact System: Supports interactive artifacts display
Props Interface (Actual Implementation)
export type EmissaryOptimizedProps = {
name?: string;
avatar?: string;
assistantId: string;
initialMessage?: string;
initialDocuments?: Document[];
initialInstructions?: string;
adminEmail?: string;
senderName?: string;
userState: UserState;
currentUser: User | null;
showLoginDialog: boolean;
setShowLoginDialog: (show: boolean) => void;
handleLogout: () => Promise<void>;
tools?: string[];
toolConfigs?: Record<string, Record<string, any>>;
accessControl?: AccessControl;
templateId?: string;
};
Usage Example
import { EmissaryOptimized } from '@/components/EmissaryOptimized';
function ChatPage() {
return (
<EmissaryOptimized
assistantId="data-analyst"
showArtifacts={true}
showThinking={false}
onMessageUpdate={(messages) => {
// Handle message updates
console.log('Messages updated:', messages.length);
}}
/>
);
}
Context Architecture
The Emissary system uses a sophisticated multi-context architecture for optimal performance and state management.
Context Hierarchy (Actual Implementation)
<OptimizedContextProvider>
<MessageStreamingProvider>
<ToolConfirmationProvider>
<EmissaryOptimized />
</ToolConfirmationProvider>
</MessageStreamingProvider>
</OptimizedContextProvider>
Note: The actual implementation uses OptimizedContextProvider that wraps multiple specialized contexts internally. The component accesses these via optimized hooks like useMessageState(), useStreaming(), useToolState(), etc.
Context Responsibilities
OptimizedContextProvider
- Purpose: Root context wrapper with performance optimizations
- Features: Memoization, batched updates, subscription management
- Use Case: Wraps entire Emissary component tree
SessionContext
- Purpose: User authentication and session management
- Features: Firebase auth integration, user state, permissions
- Data: User profile, authentication status, session metadata
AssistantContext
- Purpose: Assistant configuration and metadata management
- Features: Assistant loading, configuration caching, validation
- Data: Assistant config, capabilities, model settings
MessageStateContext
- Purpose: Core message state management
- Features: Message CRUD operations, history management, validation
- Data: Message array, loading states, error handling
MessageStreamingContext (Actual Implementation)
- Purpose: Real-time streaming message handling and Firestore integration
- Features: Reducer-based state management, abort controllers, automatic session management
- Interface:
interface MessageStreamingContextType { // State messages: Message[]; streamingMessage: LocalChatMessage | null; effectiveThinkingContent: string; isStreaming: boolean; error: string | null; sessionId: string; traceId: string | null; selectedItems?: SelectedItem[]; // Actions sendMessage: (text: string) => Promise<void>; sendToolConfirmationMessage: ( userMessage: string, confirmedTools: FirstImpressionTool[], traceId: string, signal: AbortSignal ) => Promise<void>; stopStreaming: () => void; clearError: () => void; }
ToolStateContext
- Purpose: Tool configuration and permission management
- Features: Tool availability, permission checking, configuration
- Data: Available tools, user permissions, tool settings
ToolConfirmationContext (Actual Implementation)
- Purpose: Tool confirmation workflow management
- Features: Confirmation state management, deduplication, abort handling
- Interface:
interface ToolConfirmationState { isWaitingForConfirmation: boolean; pendingTools: FirstImpressionTool[] | null; confirmedTools: FirstImpressionTool[] | null; confirmationId: string | null; isProcessingConfirmation: boolean; } // Actions setPendingConfirmation(tools, confirmationId) confirmTools(tools) rejectTools() processToolConfirmation(assistantConfig) abortToolConfirmation() clearConfirmation()
ArtifactContext
- Purpose: Artifact display and interaction management
- Features: Artifact rendering, editing, persistence
- Data: Active artifacts, editing state, display preferences
State Management Patterns
Message State Flow
// Message creation flow
const addMessage = useCallback(async (content: string) => {
// 1. Add optimistic message
const tempMessage = createTempMessage(content);
updateMessages(prev => [...prev, tempMessage]);
// 2. Send to backend via streaming
const stream = await sendToVACService(content, chatHistory);
// 3. Process streaming response
for await (const chunk of stream) {
if (chunk.type === 'content') {
updateStreamingContent(chunk.content);
} else if (chunk.type === 'tool_confirmation') {
triggerToolConfirmation(chunk.tools);
}
}
// 4. Finalize and persist
const finalMessage = finalizeMSesage(tempMessage, streamContent);
await persistToFirestore(finalMessage);
}, [chatHistory, updateMessages]);
Tool Confirmation Flow
// Tool confirmation workflow
const handleToolConfirmation = useCallback(async (tools: Tool[]) => {
// 1. Show confirmation dialog
setToolConfirmationState({
show: true,
tools: tools,
onConfirm: async (approvedTools) => {
// 2. Send confirmation to backend
await continueWithTools(approvedTools);
setToolConfirmationState({ show: false });
},
onDeny: () => {
// 3. Cancel tool execution
cancelToolExecution();
setToolConfirmationState({ show: false });
}
});
}, []);
Component Variants
EmissaryChoose - Assistant Selection
Purpose: Provides a carousel interface for selecting assistants from available templates.
Features:
- Visual assistant thumbnails with avatars
- Category filtering and search
- Assistant capability preview
- Quick selection and launch
Usage:
<EmissaryChoose
onAssistantSelect={(assistantId) => {
// Navigate to selected assistant
router.push(`/assistant/${assistantId}`);
}}
showCategories={true}
filterByUserPermissions={true}
/>
EmissaryList - Full Management Interface
Purpose: Comprehensive assistant management with list/grid views, creation, editing, and organization.
Features:
- List and grid view modes
- Assistant creation and editing
- Bulk operations and organization
- Advanced filtering and sorting
- Permission management integration
Usage:
<EmissaryList
viewMode="grid"
showCreateButton={true}
allowEditing={userPermissions.canEdit}
onAssistantUpdate={(assistant) => {
// Handle assistant updates
invalidateAssistantCache(assistant.id);
}}
/>
EmissaryListCompact - Space-Efficient Display
Purpose: Compact display variant for sidebars, mobile views, or embedded contexts.
Features:
- Minimal visual footprint
- Essential information only
- Quick actions and navigation
- Responsive design optimized
EmissaryListMinimal - Essential Display
Purpose: Minimal variant showing only basic assistant information and selection.
Features:
- Text-only display
- Basic selection functionality
- Optimal for accessibility
- Minimal resource usage
Integration Patterns
Frontend Page Integration
// In app/assistant/[assistantId]/page.tsx
import { EmissaryOptimized } from '@/components/EmissaryOptimized';
export default function AssistantPage({ params }: { params: { assistantId: string } }) {
return (
<OptimizedContextProvider>
<div className="flex h-screen">
<Sidebar />
<main className="flex-1">
<EmissaryOptimized
assistantId={params.assistantId}
showArtifacts={true}
showThinking={false}
/>
</main>
</div>
</OptimizedContextProvider>
);
}
Shared Page Integration
// In components/SharePage.tsx
import { EmissaryOptimized } from '@/components/EmissaryOptimized';
export function SharePage({ shareData }: { shareData: ShareData }) {
return (
<EmissaryOptimized
assistantId={shareData.assistantId}
initialMessages={shareData.messages}
showArtifacts={shareData.includeArtifacts}
className="h-full w-full"
/>
);
}
Performance Optimizations
Context Optimization Strategies
- Selective Subscriptions: Components subscribe only to relevant context slices
- Memoization: Expensive computations are memoized across renders
- Batch Updates: Multiple state updates are batched to prevent cascading renders
- Lazy Loading: Contexts are loaded on-demand when components need them
Component-Level Optimizations
// Example optimization patterns
const EmissaryOptimized = memo(({ assistantId, ...props }) => {
// Memoize expensive calculations
const assistantConfig = useMemo(() =>
getAssistantConfig(assistantId), [assistantId]
);
// Debounce frequent updates
const debouncedUpdate = useMemo(() =>
debounce(updateMessages, 300), []
);
// Use callback refs for dynamic elements
const messageContainerRef = useCallback((node) => {
if (node) {
// Scroll to bottom on new messages
node.scrollTop = node.scrollHeight;
}
}, [messages.length]);
return (
<div ref={messageContainerRef}>
{/* Component content */}
</div>
);
});
Styling and Theming
Tailwind Integration
The Emissary components use a consistent Tailwind CSS design system:
// Standard styling patterns
const emissaryStyles = {
container: "flex flex-col h-full bg-background text-foreground",
messageList: "flex-1 overflow-y-auto px-4 py-6 space-y-4",
inputArea: "border-t border-border p-4 bg-card",
loadingSpinner: "animate-spin h-6 w-6 text-primary",
errorBoundary: "bg-destructive/10 border border-destructive/20 rounded-lg p-4"
};
Component Variants
// Responsive variants
const variants = {
default: "w-full max-w-4xl mx-auto",
compact: "w-full max-w-2xl",
fullscreen: "w-full h-screen",
embedded: "w-full h-96 min-h-96"
};
Error Handling
Error Boundaries
// EmissaryErrorBoundary component
export function EmissaryErrorBoundary({ children }: { children: React.ReactNode }) {
return (
<ErrorBoundary
fallback={(error, retry) => (
<div className="p-8 text-center">
<h2 className="text-lg font-semibold mb-2">Something went wrong</h2>
<p className="text-muted-foreground mb-4">{error.message}</p>
<Button onClick={retry}>Try again</Button>
</div>
)}
>
{children}
</ErrorBoundary>
);
}
Graceful Degradation
// Service degradation patterns
const useEmissaryWithFallback = (assistantId: string) => {
const [error, setError] = useState<Error | null>(null);
const [fallbackMode, setFallbackMode] = useState(false);
const handleError = useCallback((error: Error) => {
console.error('Emissary error:', error);
setError(error);
// Enable fallback mode for network errors
if (error.name === 'NetworkError') {
setFallbackMode(true);
}
}, []);
return {
error,
fallbackMode,
handleError,
isOnline: navigator.onLine
};
};
Testing Strategy
Component Testing
// Example test structure
describe('EmissaryOptimized', () => {
it('should render with assistant configuration', async () => {
render(
<EmissaryOptimized
assistantId="test-assistant"
showArtifacts={true}
/>
);
await waitFor(() => {
expect(screen.getByRole('main')).toBeInTheDocument();
});
});
it('should handle message sending', async () => {
const mockSend = vi.fn();
render(<EmissaryOptimized onMessageUpdate={mockSend} />);
const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'Hello' } });
fireEvent.submit(input.closest('form')!);
await waitFor(() => {
expect(mockSend).toHaveBeenCalled();
});
});
});
Context Testing
// Context testing patterns
describe('MessageStateContext', () => {
it('should manage message state correctly', () => {
const { result } = renderHook(() => useMessageState(), {
wrapper: MessageStateProvider
});
act(() => {
result.current.addMessage('Hello');
});
expect(result.current.messages).toHaveLength(1);
expect(result.current.messages[0].content).toBe('Hello');
});
});
Migration and Upgrade Guide
From Legacy Components
If migrating from older assistant components:
- Update imports to use new component paths
- Wrap with contexts using
OptimizedContextProvider - Update prop interfaces to match new API
- Test streaming integration thoroughly
- Verify tool confirmation workflow
Breaking Changes
v2.0 Changes:
- Context architecture requires explicit provider wrapping
- Message format includes additional metadata fields
- Tool confirmation is now required by default
- Authentication context is mandatory
Best Practices
Development Guidelines
- Always wrap with contexts: Use
OptimizedContextProviderfor all Emissary components - Handle loading states: Provide appropriate loading indicators
- Implement error boundaries: Graceful error handling is essential
- Optimize subscriptions: Subscribe only to needed context slices
- Test streaming scenarios: Ensure proper handling of real-time updates
Performance Recommendations
- Memoize expensive operations: Use
useMemofor complex calculations - Debounce user inputs: Prevent excessive API calls
- Lazy load components: Use React.lazy for large assistant lists
- Monitor context updates: Use dev tools to identify unnecessary re-renders
- Optimize bundle size: Tree-shake unused Emissary variants
Accessibility Guidelines
- Keyboard navigation: Full keyboard support for all interactions
- Screen reader support: Proper ARIA labels and descriptions
- Focus management: Logical focus flow during interactions
- Color contrast: Meet WCAG 2.1 AA standards
- Reduced motion: Respect user motion preferences
Troubleshooting
Common Issues
Context Not Available Error
Error: useMessageState must be used within a MessageStateProvider
Solution: Wrap component with OptimizedContextProvider
Streaming Connection Failures
Error: Failed to connect to streaming endpoint
Solution: Check network connectivity and backend service status
Tool Confirmation Timeout
Error: Tool confirmation timed out
Solution: Increase timeout or implement retry logic
Memory Leaks in Development
Warning: Memory leak detected in component
Solution: Ensure proper cleanup in useEffect hooks
Debugging Tools
- React Developer Tools: Inspect context state and prop changes
- Network Tab: Monitor streaming connections and API calls
- Console Logging: Enable debug mode with
NEXT_PUBLIC_DEBUG=true - Performance Profiler: Identify rendering bottlenecks
Future Roadmap
Planned Enhancements
- Enhanced Accessibility: WCAG 2.1 AAA compliance
- Offline Support: Service worker integration for offline functionality
- Advanced Theming: Dynamic theme switching and custom themes
- Plugin System: Extensible architecture for custom tools and features
- Real-time Collaboration: Multi-user assistant sessions
- Mobile Optimization: Native mobile app integration