@wix/ai-assistant-avatar
v0.42.0
Published
`@wix/ai-assistant-avatar` is a React component library that provides an animated avatar (Astro) for AI assistant interfaces. The avatar uses `@wix/aria-ai-avatar` to provide smooth, interactive animations including eye tracking, state transitions, and mo
Maintainers
Keywords
Readme
@wix/ai-assistant-avatar Usage Guide
Overview
@wix/ai-assistant-avatar is a React component library that provides an animated avatar (Astro) for AI assistant interfaces. The avatar uses @wix/aria-ai-avatar to provide smooth, interactive animations including eye tracking, state transitions, and movement animations.
Key Features
- Animated Avatar Component: Animated character with multiple states powered by
@wix/aria-ai-avatar - Eye Tracking: Automatic eye movement following mouse cursor or typing position
- State Management: Multiple animation states (idle, loading, thinking, etc.)
- Smooth Transitions: Coordinated animations for moving between UI positions
- Imperative API: Full control via ref methods for programmatic animation control
Installation
yarn add @wix/ai-assistant-avatarPeer Dependencies
react:^16.8.0or higherreact-dom:^16.8.0or higher
Runtime Dependencies
The package includes these dependencies (automatically installed):
@wix/aria-ai-avatar: Avatar animation runtimeclassnames: CSS class name utilities
Basic Usage
import React, { useRef } from 'react';
import { AiAssistantAvatar, AstroRef } from '@wix/ai-assistant-avatar';
function MyComponent() {
const avatarRef = useRef<AstroRef>(null);
return (
<div>
<AiAssistantAvatar ref={avatarRef} />
<button onClick={() => avatarRef.current?.trigger.idle()}>
Reset to Idle
</button>
</div>
);
}API Reference
Component Props (AstroProps)
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| mode | 'light' \| 'dark' | lib default | Color mode of the avatar |
| variant | '2d' \| '3d' | lib default | Visual variant of the avatar |
The avatar uses fixed dimensions of 94x94 pixels (defined in ASTRO_SIZE).
Ref API (AstroRef)
The component exposes an imperative handle via ref:
Animation Triggers
trigger: {
idle: () => Promise<void>; // Switch to idle state
undo: () => Promise<void>; // Undo event animation
ideaSpark: () => Promise<void>; // Idea spark event animation
boredom: () => Promise<void>; // Toggle between SLEEPING and IDLE states
bigLoader: () => Promise<void>; // Toggle between BIG_LOADER and IDLE states
smallLoader: () => Promise<void>; // Toggle to SMALL_LOADER (from SMALL state)
shrink: () => Promise<void>; // Toggle between SMALL and IDLE states
publish: () => Promise<void>; // Publish event animation
}Visibility & Interaction
hide: () => void; // Hide the avatar
show: () => void; // Show the avatar
onMouseMove: (clientX: number, clientY: number) => void; // Track mouse for eye movement
onUserTyping: (caretX: number, caretY: number) => void; // Track typing position
onTravelHomeFrom: (x: number, y: number, options: MoveOptions) => Promise<void>; // Animate from offset TO home position
onTravelAwayTo: (x: number, y: number, options: MoveOptions) => Promise<void>; // Animate from home TO offset position
changeAstroColor: (color: string) => void; // Change avatar color using any valid CSS color string (e.g. '#ff0000', 'rgb(255,0,0)')
toggleAvatarBackground: () => void; // Toggle avatar background visibilityState Properties
avatarHidden: boolean; // Whether the avatar is hidden
getState: () => AvatarState; // Get current animation state
wrapper: HTMLDivElement | null; // DOM wrapper elementExamples
Example 1: State-Based Chat Avatar
Complete example showing how to control avatar animations based on chat state, with mouse tracking:
import React, { useRef, useEffect } from 'react';
import { AiAssistantAvatar, AstroRef } from '@wix/ai-assistant-avatar';
function ChatAvatar({
isLoading,
hasNewMessage
}: {
isLoading: boolean;
hasNewMessage: boolean;
}) {
const avatarRef = useRef<AstroRef>(null);
// Update avatar state based on chat status
useEffect(() => {
if (isLoading) {
avatarRef.current?.trigger.bigLoader();
} else if (hasNewMessage) {
avatarRef.current?.trigger.ideaSpark();
setTimeout(() => avatarRef.current?.trigger.idle(), 1000);
} else {
avatarRef.current?.trigger.idle();
}
}, [isLoading, hasNewMessage]);
// Enable eye tracking with mouse movement
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
avatarRef.current?.onMouseMove(e.clientX, e.clientY);
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return <AiAssistantAvatar ref={avatarRef} />;
}Example 2: Single Instance with Portal Pattern
For complex UIs where the avatar moves between positions (input box, messages, welcome screen), use a single avatar instance with React portals:
import React, { useRef, useState, useCallback, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { AiAssistantAvatar, AstroRef, AvatarState } from '@wix/ai-assistant-avatar';
function AvatarManager() {
const avatarRef = useRef<AstroRef>(null);
const inputSlotRef = useRef<HTMLDivElement>(null);
const messageSlotRef = useRef<HTMLDivElement>(null);
const [activeSlot, setActiveSlot] = useState<HTMLDivElement | null>(null);
const [previousPosition, setPreviousPosition] = useState<DOMRect | null>(null);
const [moveOptions, setMoveOptions] = useState({ endState: AvatarState.IDLE });
// Animate when slot changes
useEffect(() => {
if (!avatarRef.current || !activeSlot) return;
const currentRect = avatarRef.current.wrapper?.getBoundingClientRect();
if (currentRect) {
const deltaX = previousPosition ? previousPosition.x - currentRect.x : 0;
const deltaY = previousPosition ? previousPosition.y - currentRect.y : 0;
avatarRef.current.onTravelHomeFrom(deltaX, deltaY, moveOptions).then(() => {
setPreviousPosition(currentRect);
});
}
}, [activeSlot]);
const moveToMessage = useCallback(() => {
if (!messageSlotRef.current) return;
// Capture current position before moving
const currentRect = avatarRef.current?.wrapper?.getBoundingClientRect();
if (currentRect) setPreviousPosition(currentRect);
setMoveOptions({ endState: AvatarState.SMALL_LOADER });
setActiveSlot(messageSlotRef.current);
}, []);
const moveToInput = useCallback(() => {
if (!inputSlotRef.current) return;
const currentRect = avatarRef.current?.wrapper?.getBoundingClientRect();
if (currentRect) setPreviousPosition(currentRect);
setMoveOptions({ endState: AvatarState.IDLE });
setActiveSlot(inputSlotRef.current);
}, []);
return (
<>
{/* Slot placeholders - avatar will portal into the active one */}
<div ref={inputSlotRef} />
<div ref={messageSlotRef} />
{/* Single avatar instance rendered via portal */}
{activeSlot && createPortal(
<AiAssistantAvatar ref={avatarRef} />,
activeSlot
)}
<button onClick={moveToMessage}>Move to Message</button>
<button onClick={moveToInput}>Move to Input</button>
</>
);
}Example 3: Typing Position Tracking
Track user's typing position so avatar eyes follow the caret:
function InputWithAvatar() {
const avatarRef = useRef<AstroRef>(null);
const inputRef = useRef<HTMLInputElement>(null);
const handleInputChange = () => {
const input = inputRef.current;
if (!input || !avatarRef.current) return;
// Get caret position
const selectionStart = input.selectionStart || 0;
const rect = input.getBoundingClientRect();
// Calculate approximate caret position
const textBeforeCaret = input.value.substring(0, selectionStart);
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (context) {
context.font = getComputedStyle(input).font;
const textWidth = context.measureText(textBeforeCaret).width;
const caretX = rect.left + textWidth;
const caretY = rect.top + rect.height / 2;
// Avatar eyes will follow the caret
avatarRef.current.onUserTyping(caretX, caretY);
}
};
return (
<div>
<input ref={inputRef} onChange={handleInputChange} />
<AiAssistantAvatar ref={avatarRef} />
</div>
);
}Configuration Constants
Animation States (AvatarState)
The AvatarState enum is exported from @wix/ai-assistant-avatar:
import { AvatarState } from '@wix/ai-assistant-avatar';
AvatarState.IDLE // Default idle state
AvatarState.SMALL // Shrunk/minimized state
AvatarState.SMALL_LOADER // Small loading indicator
AvatarState.BIG_LOADER // Large loading indicator
AvatarState.SLEEPING // Boredom/sleeping stateSize Constants (ASTRO_SIZE)
ASTRO_SIZE = {
WIDTH: 94,
HEIGHT: 94,
}Advanced Patterns
Visibility Control
Control avatar visibility programmatically:
// Hide/show avatar
avatarRef.current?.hide();
avatarRef.current?.show();
// Check if hidden
const isHidden = avatarRef.current?.avatarHidden;Background Toggle
Toggle avatar background visibility:
avatarRef.current?.toggleAvatarBackground();Best Practices
- Single Instance with Portals: Use a single avatar instance with
createPortalfor moving between UI positions - Animation Coordination: Use the appropriate travel method based on direction:
onTravelHomeFrom(x, y)- Avatar arriving: animates FROM offset TO home positiononTravelAwayTo(x, y)- Avatar departing: animates FROM home TO offset position
- Performance: Control visibility with
hide()/show()methods - State Management: Use
AvatarStateenum instead of magic strings - Error Handling: Wrap animation calls in try-catch blocks
Troubleshooting
Avatar not appearing: Ensure ref is initialized and the portal target element exists in the DOM
Animations not triggering: Verify ref is attached and methods are called after mount
Eye tracking not working: Ensure onMouseMove receives viewport coordinates (clientX, clientY)
Position transitions not smooth: Check that onTravelHomeFrom delta coordinates are calculated correctly (previous position minus current position)
Portal not rendering: Ensure activeSlot state is set to a valid DOM element before rendering the portal
TypeScript Support
The package includes full TypeScript definitions:
import type { AstroRef, AstroProps, Position, MoveOptions } from '@wix/ai-assistant-avatar';
import { AvatarState } from '@wix/ai-assistant-avatar';Additional Resources
- Example Implementation: See
packages/ai-assistant-chat-uifor a complete integration example with the portal pattern
