Build responsive, interactive applications with comprehensive event-driven architecture. Real-time feedback for all operations gives you precise control over the user experience.
import { AnimusClient } from 'animus-client';
const client = new AnimusClient({
tokenProviderUrl: 'https://your-backend.com/api/get-animus-token',
chat: {
model: 'vivian-llama3.1-70b-1.0-fp8',
systemMessage: 'You are a helpful assistant.',
autoTurn: true,
check_image_generation: true
}
});
// Listen to all events
client.on('messageStart', (data) => console.log('Message started:', data));
client.on('messageComplete', (data) => console.log('Message completed:', data));
client.on('messageError', (data) => console.error('Message error:', data));
client.on('imageGenerationStart', (data) => console.log('Image generation started:', data));
client.on('imageGenerationComplete', (data) => console.log('Image generated:', data));
client.on('imageGenerationError', (data) => console.error('Image generation error:', data));
// Message Event Data Structure (for chat.send() events)
interface MessageEventData {
content: string; // The message content
messageType?: 'regular' | 'auto' | 'followup'; // Type of message
turnIndex?: number; // For auto-turn: current turn index (0-based)
totalTurns?: number; // For auto-turn: total number of turns
totalMessages?: number; // When all messages in sequence are complete
timestamp: string; // ISO timestamp
error?: string; // For error events only
}
// Chat Completions Response Structure (for await chat.completions())
interface ChatCompletionResponse {
id: string; // Unique response ID
object: 'chat.completion'; // Response type identifier
created: number; // Unix timestamp
model: string; // Model used for completion
choices: Array<{
message: {
role: 'assistant';
content: string | null; // Main response text (null if only tool_calls)
reasoning?: string; // Model's reasoning process (optional)
tool_calls?: ToolCall[]; // Function calls requested (optional)
image_prompt?: string; // Image generation prompt (optional)
turns?: string[]; // Conversation turns for autoTurn (optional)
next?: boolean; // Indicates follow-up expected (optional)
};
index: number;
finish_reason: string;
}>;
usage?: { // Token usage statistics (optional)
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
compliance_violations?: string[]; // Any compliance issues (optional)
}
// Message starts (typing begins)
client.on('messageStart', (data: MessageEventData) => {
console.log(`Starting ${data.messageType || 'regular'} message`);
if (data.messageType === 'auto') {
showTypingIndicator(`Turn ${data.turnIndex! + 1}/${data.totalTurns}`);
} else if (data.messageType === 'followup') {
showFollowUpIndicator();
} else {
showTypingIndicator();
}
});
// Message completes (content ready)
client.on('messageComplete', (data: MessageEventData) => {
console.log(`Completed ${data.messageType || 'regular'} message`);
hideTypingIndicator();
displayMessage(data.content, data.messageType);
// Check if this is the final message in a sequence
if (data.totalMessages) {
console.log(`All ${data.totalMessages} messages completed`);
onSequenceComplete();
}
});
// Message error (something went wrong)
client.on('messageError', (data: MessageEventData) => {
console.error(`${data.messageType || 'regular'} message error:`, data.error);
hideTypingIndicator();
// Handle cancellations gracefully
if (data.error?.includes('Canceled')) {
console.log('Message was canceled due to new user input');
} else {
showErrorMessage(data.error || 'Unknown error');
}
});
// Track auto-turn progress
client.on('messageStart', (data) => {
if (data.messageType === 'auto') {
updateTurnProgress(data.turnIndex! + 1, data.totalTurns!);
}
});
client.on('messageComplete', (data) => {
if (data.messageType === 'auto') {
console.log(`Turn ${data.turnIndex! + 1}/${data.totalTurns} completed`);
// Update progress bar
const progress = ((data.turnIndex! + 1) / data.totalTurns!) * 100;
updateProgressBar(progress);
}
});
function updateTurnProgress(current: number, total: number) {
const progressElement = document.getElementById('turn-progress');
if (progressElement) {
progressElement.textContent = `Turn ${current} of ${total}`;
}
}
// Image generation event data structure
interface ImageGenerationEventData {
prompt: string; // The image prompt
imageUrl?: string; // Generated image URL (on complete)
error?: string; // Error message (on error)
timestamp: string; // ISO timestamp
}
// Image generation starts
client.on('imageGenerationStart', (data: ImageGenerationEventData) => {
console.log('Starting image generation:', data.prompt);
showImageGenerationUI();
updateImagePrompt(data.prompt);
startImageLoadingAnimation();
});
// Image generation completes
client.on('imageGenerationComplete', (data: ImageGenerationEventData) => {
console.log('Image generated:', data.imageUrl);
stopImageLoadingAnimation();
displayGeneratedImage(data.imageUrl!);
addImageToHistory(data.imageUrl!, data.prompt);
});
// Image generation fails
client.on('imageGenerationError', (data: ImageGenerationEventData) => {
console.error('Image generation failed:', data.error);
stopImageLoadingAnimation();
showImageGenerationError(data.error!);
hideImageGenerationUI();
});
class ImageGenerationManager {
private isGenerating = false;
private currentPrompt = '';
constructor() {
this.setupEventListeners();
}
setupEventListeners() {
client.on('imageGenerationStart', (data) => {
this.isGenerating = true;
this.currentPrompt = data.prompt;
this.showGenerationProgress();
});
client.on('imageGenerationComplete', (data) => {
this.isGenerating = false;
this.showGeneratedImage(data.imageUrl!, this.currentPrompt);
});
client.on('imageGenerationError', (data) => {
this.isGenerating = false;
this.showGenerationError(data.error!);
});
}
showGenerationProgress() {
const container = document.getElementById('image-container');
container!.innerHTML = `
<div class="image-generating">
<div class="loading-spinner"></div>
<p>Generating image...</p>
<small>${this.currentPrompt.substring(0, 100)}...</small>
</div>
`;
}
showGeneratedImage(imageUrl: string, prompt: string) {
const container = document.getElementById('image-container');
container!.innerHTML = `
<div class="generated-image-container">
<img src="${imageUrl}" alt="${prompt}" class="generated-image" />
<p class="image-prompt">${prompt}</p>
</div>
`;
}
showGenerationError(error: string) {
const container = document.getElementById('image-container');
container!.innerHTML = `
<div class="image-error">
<p>Failed to generate image: ${error}</p>
<button onclick="this.retryGeneration()">Try Again</button>
</div>
`;
}
isCurrentlyGenerating(): boolean {
return this.isGenerating;
}
}
class ConversationStateManager {
private state = {
isTyping: false,
isGeneratingImage: false,
currentTurn: 0,
totalTurns: 0,
pendingOperations: 0
};
constructor() {
this.setupEventListeners();
}
setupEventListeners() {
// Track typing state
client.on('messageStart', (data) => {
this.state.isTyping = true;
this.state.pendingOperations++;
if (data.messageType === 'auto') {
this.state.currentTurn = data.turnIndex! + 1;
this.state.totalTurns = data.totalTurns!;
}
this.updateUI();
});
client.on('messageComplete', (data) => {
this.state.pendingOperations--;
if (data.totalMessages) {
this.state.isTyping = false;
this.state.currentTurn = 0;
this.state.totalTurns = 0;
}
this.updateUI();
});
// Track image generation state
client.on('imageGenerationStart', () => {
this.state.isGeneratingImage = true;
this.state.pendingOperations++;
this.updateUI();
});
client.on('imageGenerationComplete', () => {
this.state.isGeneratingImage = false;
this.state.pendingOperations--;
this.updateUI();
});
client.on('imageGenerationError', () => {
this.state.isGeneratingImage = false;
this.state.pendingOperations--;
this.updateUI();
});
}
updateUI() {
const statusElement = document.getElementById('conversation-status');
if (this.state.isTyping && this.state.isGeneratingImage) {
statusElement!.textContent = 'AI is typing and generating an image...';
} else if (this.state.isTyping) {
if (this.state.totalTurns > 1) {
statusElement!.textContent = `AI is typing (${this.state.currentTurn}/${this.state.totalTurns})...`;
} else {
statusElement!.textContent = 'AI is typing...';
}
} else if (this.state.isGeneratingImage) {
statusElement!.textContent = 'Generating image...';
} else if (this.state.pendingOperations > 0) {
statusElement!.textContent = 'Processing...';
} else {
statusElement!.textContent = 'Ready';
}
// Update input state
const inputElement = document.getElementById('message-input') as HTMLInputElement;
inputElement.disabled = this.state.pendingOperations > 0;
}
getState() {
return { ...this.state };
}
}
class EventRouter {
private handlers = new Map<string, Function[]>();
constructor() {
this.setupRouting();
}
setupRouting() {
// Route all events through the router
client.on('messageStart', (data) => this.route('messageStart', data));
client.on('messageComplete', (data) => this.route('messageComplete', data));
client.on('messageError', (data) => this.route('messageError', data));
client.on('imageGenerationStart', (data) => this.route('imageGenerationStart', data));
client.on('imageGenerationComplete', (data) => this.route('imageGenerationComplete', data));
client.on('imageGenerationError', (data) => this.route('imageGenerationError', data));
}
// Register handlers for specific event types
on(eventType: string, handler: Function) {
if (!this.handlers.has(eventType)) {
this.handlers.set(eventType, []);
}
this.handlers.get(eventType)!.push(handler);
}
// Register handlers for specific message types
onMessageType(messageType: 'regular' | 'auto' | 'followup', eventType: string, handler: Function) {
this.on(eventType, (data: any) => {
if (data.messageType === messageType || (!data.messageType && messageType === 'regular')) {
handler(data);
}
});
}
private route(eventType: string, data: any) {
const handlers = this.handlers.get(eventType) || [];
handlers.forEach(handler => {
try {
handler(data);
} catch (error) {
console.error(`Error in event handler for ${eventType}:`, error);
}
});
}
}
// Usage
const eventRouter = new EventRouter();
// Handle only auto-turn messages
eventRouter.onMessageType('auto', 'messageStart', (data) => {
console.log('Auto-turn message started:', data);
});
// Handle only regular messages
eventRouter.onMessageType('regular', 'messageComplete', (data) => {
console.log('Regular message completed:', data);
});
// Handle all image generation events
eventRouter.on('imageGenerationStart', (data) => {
console.log('Any image generation started:', data);
});
class ErrorManager {
private errorHistory: Array<{type: string, error: string, timestamp: string}> = [];
constructor() {
this.setupErrorHandling();
}
setupErrorHandling() {
// Handle message errors
client.on('messageError', (data) => {
this.logError('message', data.error!, data.timestamp);
if (data.error?.includes('Canceled')) {
this.handleCancellation(data);
} else if (data.error?.includes('Authentication')) {
this.handleAuthError(data);
} else if (data.error?.includes('Rate limit')) {
this.handleRateLimit(data);
} else {
this.handleGenericError(data);
}
});
// Handle image generation errors
client.on('imageGenerationError', (data) => {
this.logError('imageGeneration', data.error!, data.timestamp);
if (data.error?.includes('content policy')) {
this.handleContentPolicyError(data);
} else if (data.error?.includes('timeout')) {
this.handleTimeoutError(data);
} else {
this.handleImageGenerationError(data);
}
});
}
private logError(type: string, error: string, timestamp: string) {
this.errorHistory.push({ type, error, timestamp });
// Keep only last 50 errors
if (this.errorHistory.length > 50) {
this.errorHistory.shift();
}
}
private handleCancellation(data: any) {
console.log('Operation was canceled:', data);
// Don't show error UI for cancellations
}
private handleAuthError(data: any) {
console.error('Authentication error:', data);
this.showAuthErrorDialog();
}
private handleRateLimit(data: any) {
console.warn('Rate limit exceeded:', data);
this.showRateLimitWarning();
}
private handleContentPolicyError(data: any) {
console.warn('Content policy violation:', data);
this.showContentPolicyWarning();
}
private handleTimeoutError(data: any) {
console.warn('Operation timed out:', data);
this.showTimeoutWarning();
}
private handleGenericError(data: any) {
console.error('Generic error:', data);
this.showGenericErrorDialog(data.error);
}
private handleImageGenerationError(data: any) {
console.error('Image generation error:', data);
this.showImageErrorDialog(data.error);
}
// UI methods
private showAuthErrorDialog() {
// Implement auth error UI
}
private showRateLimitWarning() {
// Implement rate limit UI
}
private showContentPolicyWarning() {
// Implement content policy UI
}
private showTimeoutWarning() {
// Implement timeout UI
}
private showGenericErrorDialog(error: string) {
// Implement generic error UI
}
private showImageErrorDialog(error: string) {
// Implement image error UI
}
getErrorHistory() {
return [...this.errorHistory];
}
}
class EventPerformanceMonitor {
private eventTimes = new Map<string, number>();
private eventCounts = new Map<string, number>();
constructor() {
this.setupMonitoring();
}
setupMonitoring() {
const events = [
'messageStart', 'messageComplete', 'messageError',
'imageGenerationStart', 'imageGenerationComplete', 'imageGenerationError'
];
events.forEach(eventType => {
client.on(eventType, (data) => {
this.recordEvent(eventType, data.timestamp);
});
});
}
private recordEvent(eventType: string, timestamp: string) {
const now = Date.now();
const eventTime = new Date(timestamp).getTime();
const latency = now - eventTime;
// Record latency
this.eventTimes.set(eventType, latency);
// Count events
const count = this.eventCounts.get(eventType) || 0;
this.eventCounts.set(eventType, count + 1);
// Log slow events
if (latency > 1000) {
console.warn(`Slow event detected: ${eventType} took ${latency}ms`);
}
}
getStats() {
return {
eventTimes: Object.fromEntries(this.eventTimes),
eventCounts: Object.fromEntries(this.eventCounts)
};
}
logStats() {
console.table(this.getStats());
}
}
class EventDebugger {
private isEnabled = false;
private eventLog: Array<{type: string, data: any, timestamp: number}> = [];
enable() {
this.isEnabled = true;
this.setupLogging();
}
disable() {
this.isEnabled = false;
}
private setupLogging() {
if (!this.isEnabled) return;
const events = [
'messageStart', 'messageComplete', 'messageError',
'imageGenerationStart', 'imageGenerationComplete', 'imageGenerationError'
];
events.forEach(eventType => {
client.on(eventType, (data) => {
this.logEvent(eventType, data);
});
});
}
private logEvent(type: string, data: any) {
if (!this.isEnabled) return;
const logEntry = {
type,
data: { ...data },
timestamp: Date.now()
};
this.eventLog.push(logEntry);
console.log(`[EVENT] ${type}:`, data);
// Keep only last 100 events
if (this.eventLog.length > 100) {
this.eventLog.shift();
}
}
getEventLog() {
return [...this.eventLog];
}
exportLog() {
const logData = JSON.stringify(this.eventLog, null, 2);
const blob = new Blob([logData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `animus-event-log-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
}
// Usage
const debugger = new EventDebugger();
debugger.enable(); // Start logging events
// Organize event handlers by feature
class ChatEventHandlers {
static setupMessageHandlers() {
client.on('messageStart', this.onMessageStart);
client.on('messageComplete', this.onMessageComplete);
client.on('messageError', this.onMessageError);
}
static onMessageStart(data: any) {
// Handle message start
}
static onMessageComplete(data: any) {
// Handle message complete
}
static onMessageError(data: any) {
// Handle message error
}
}
class ImageEventHandlers {
static setupImageHandlers() {
client.on('imageGenerationStart', this.onImageStart);
client.on('imageGenerationComplete', this.onImageComplete);
client.on('imageGenerationError', this.onImageError);
}
static onImageStart(data: any) {
// Handle image generation start
}
static onImageComplete(data: any) {
// Handle image generation complete
}
static onImageError(data: any) {
// Handle image generation error
}
}
// Initialize all handlers
ChatEventHandlers.setupMessageHandlers();
ImageEventHandlers.setupImageHandlers();
// Clean up event listeners when needed
class EventManager {
private listeners: Array<{event: string, handler: Function}> = [];
addListener(event: string, handler: Function) {
client.on(event, handler);
this.listeners.push({ event, handler });
}
removeAllListeners() {
this.listeners.forEach(({ event, handler }) => {
client.off(event, handler);
});
this.listeners = [];
}
cleanup() {
this.removeAllListeners();
}
}
// Use in components that may be destroyed
const eventManager = new EventManager();
eventManager.addListener('messageComplete', handleMessageComplete);
// Clean up when component unmounts
window.addEventListener('beforeunload', () => {
eventManager.cleanup();
});