# @wix/ai-assistant-avatar Usage Guide &nbsp;&nbsp;<img width="34" height="34" alt="Avatar (1)" src="https://github.com/user-attachments/assets/3f667860-eaa6-4cc4-bb9a-1d9d6e493c3a" />

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

```bash
yarn add @wix/ai-assistant-avatar
```

### Peer Dependencies

- `react`: `^16.8.0` or higher
- `react-dom`: `^16.8.0` or higher

### Runtime Dependencies

The package includes these dependencies (automatically installed):
- `@wix/aria-ai-avatar`: Avatar animation runtime
- `classnames`: CSS class name utilities

## Basic Usage

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

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

```typescript
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 visibility
```

#### State Properties

```typescript
avatarHidden: boolean;              // Whether the avatar is hidden
getState: () => AvatarState;        // Get current animation state
wrapper: HTMLDivElement | null;     // DOM wrapper element
```

## Examples

### Example 1: State-Based Chat Avatar

Complete example showing how to control avatar animations based on chat state, with mouse tracking:

```tsx
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:

```tsx
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:

```tsx
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`:

```typescript
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 state
```

### Size Constants (`ASTRO_SIZE`)

```typescript
ASTRO_SIZE = {
  WIDTH: 94,
  HEIGHT: 94,
}
```

## Advanced Patterns

### Visibility Control

Control avatar visibility programmatically:

```tsx
// Hide/show avatar
avatarRef.current?.hide();
avatarRef.current?.show();

// Check if hidden
const isHidden = avatarRef.current?.avatarHidden;
```

### Background Toggle

Toggle avatar background visibility:

```tsx
avatarRef.current?.toggleAvatarBackground();
```

## Best Practices

1. **Single Instance with Portals**: Use a single avatar instance with `createPortal` for moving between UI positions
2. **Animation Coordination**: Use the appropriate travel method based on direction:
   - `onTravelHomeFrom(x, y)` - Avatar arriving: animates FROM offset TO home position
   - `onTravelAwayTo(x, y)` - Avatar departing: animates FROM home TO offset position
3. **Performance**: Control visibility with `hide()`/`show()` methods
4. **State Management**: Use `AvatarState` enum instead of magic strings
5. **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:

```typescript
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-ui` for a complete integration example with the portal pattern
