> For the complete documentation index, see [llms.txt](https://docs.bitte.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bitte.ai/agents/embeddable-chat-component.md).

# Embeddable Chat Component

## Example: Rhea DEX Agent

#### The first AI driven DEX on the Largest DEX on NEAR

[See on Rhea DEX](https://dex.rhea.finance/)

{% embed url="<https://www.loom.com/share/42904d8c54f9478db08022dff0ccebdb>" %}

## Bitte AI Chat

### Introduction

The **Bitte AI Chat** component is a React component that enables AI-powered chat interactions in your application. It supports both **NEAR Protocol** and **EVM blockchain** interactions through wallet integrations, allowing users to interact with smart contracts and perform transactions directly through the chat interface.

> 🔑 Before you begin, make sure you have:
>
> * A **Bitte API Key** - Get your beta `BITTE_API_KEY` [here ](https://key.bitte.ai)

***

### Quick Start (<10min)

1. Install package
2. Add the Chat Component
3. Setup API Route
4. Wallet Connection

{% embed url="<https://share.descript.com/view/wK4x9Qj5rjt>" %}

### 1. Install Package

```
pnpm install @bitte-ai/chat
```

### 2. Add the Chat Component

Import and use `BitteAiChat` in your react app and select the agent that you would like to use, browse the available agents and their respective ids on the [registry](https://www.bitte.ai/registry)\
The `apiUrl` corresponds to a proxy to not expose your api key on the client\
\
from `@bitte-ai/chat@0.19.0` onwards we have now markdown rendering, you can keep rendering plaintext, or if you wanna markdown support just add `format="markdown"` to BitteAiChat Component

```jsx

import {BitteAiChat} from "@bitte-ai/chat";
import '@bitte-ai/chat/styles.css';

<BitteAiChat  
  agentId="your-agent-id"
  format="markdown"
  apiUrl="/api/chat"
/>
```

### 3. Setup API Route

Create an API route in your Next.js application to proxy requests to the Bitte API to not expose your key.\
\
Nextjs app router implementation:

```typescript
import type { NextRequest } from 'next/server';

const {
  BITTE_API_KEY,
  BITTE_API_URL = 'https://ai-runtime-446257178793.europe-west1.run.app/chat',
} = process.env;

export const dynamic = 'force-dynamic';
export const maxDuration = 60;

export const POST = async (req: NextRequest): Promise<Response> => {
  try {
    const data = await req.json();

    const requestInit: RequestInit & { duplex: 'half' } = {
      method: 'POST',
      body: JSON.stringify(data),
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${BITTE_API_KEY}`,
      },
      duplex: 'half',
    };

    const upstreamResponse = await fetch(`${BITTE_API_URL}`, requestInit);

    const headers = new Headers(upstreamResponse.headers);
    headers.delete('Content-Encoding');

    return new Response(upstreamResponse.body, {
      status: upstreamResponse.status,
      headers,
    });
  } catch (error) {
    console.error('Error in chat API route:', error);
    return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
      status: 500,
      headers: {
        'Content-Type': 'application/json',
      },
    });
  }
};

```

Nextjs page router implementation:

```typescript
import type { NextApiRequest, NextApiResponse } from 'next';

const {
  BITTE_API_KEY,
  BITTE_API_URL = 'https://ai-runtime-446257178793.europe-west1.run.app/chat',
} = process.env;

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const data = req.body;

    const requestInit: RequestInit = {
      method: 'POST',
      body: JSON.stringify(data),
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${BITTE_API_KEY}`,
      },
    };

    const upstreamResponse = await fetch(`${BITTE_API_URL}`, requestInit);
    
    res.statusCode = upstreamResponse.status;
    
    for (const [key, value] of Object.entries(upstreamResponse.headers)) {
      if (key.toLowerCase() !== 'content-encoding') {
        res.setHeader(key, value as string);
      }
    }

    res.setHeader('Content-Type', upstreamResponse.headers.get('Content-Type') || 'application/json');
    
    if (!upstreamResponse.body) {
      return res.end();
    }

    const reader = upstreamResponse.body.getReader();
    
    async function readChunk() {
      const { done, value } = await reader.read();
      
      if (done) {
        return res.end();
      }
      
      res.write(value);
      await readChunk();
    }
    
    await readChunk();
    
  } catch (error) {
    console.error('Error in chat API route:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
} 
```

At this point the chat should already work but to be able to send transactions you will need to add a wallet connection

### 4. Add wallet connection

### EVM Integration

EVM integration uses WalletConnect with wagmi hooks:

```typescript

import { useAppKitAccount } from '@reown/appkit/react';
import { useSendTransaction, useSwitchChain } from 'wagmi';

export default function Chat() {

  const { address } = useAppKitAccount();
  const { data: hash, sendTransaction } = useSendTransaction();
  const { switchChain } = useSwitchChain();
  
  return (
    <BitteAiChat
      agentId="your-agent-id"
      apiUrl="/api/chat"
      wallet={{
        evm: { address, hash, sendTransaction, switchChain },
      }}
    />
  );
}
```

```typescript
import { Account } from "near-api-js";
// get near account instance from near-api-js by instantiating a keypair
<BitteAiChat
  agentId="your-agent-id"
  apiUrl="/api/chat"
  wallet={{ near: { account: nearAccount } }}
/>
```

#### SUI Integration

SUI integration uses WalletConnect with wagmi hooks:

```typescript
import { useWallet as useSuiWallet } from '@suiet/wallet-kit';

export default function Chat() {

  const suiWallet = useSuiWallet();

  return (
    <BitteAiChat
      agentId="your-agent-id"
      apiUrl="/api/chat"
      wallet={ sui: { wallet: suiWallet },}
    />
  );
}
```

#### NEAR Integration

You can integrate with NEAR using either the NEAR Wallet Selector or a direct account connection. If you want to be able to send near transacitons through the chat you will need to define at least one of these

**Using Wallet Selector**

```typescript
import { useBitteWallet, Wallet } from "@bitte-ai/react";
import { BitteAiChat } from "@bitte-ai/chat";
import '@bitte-ai/chat/styles.css';


export default function Chat() {
  const { selector } = useBitteWallet();
  const [wallet, setWallet] = useState<Wallet>();

  useEffect(() => {
    const fetchWallet = async () => {
      const walletInstance = await selector.wallet();
      setWallet(walletInstance);
    };
    if (selector) fetchWallet();
  }, [selector]);

  return (
    <BitteAiChat
      agentId="your-agent-id"
      apiUrl="/api/chat"
      wallet={{ near: { wallet } }}
    />
  );
}
```

### Component Props

```typescript
interface BitteAiChatProps {
  agentId: string; // ID of the AI agent to use
  apiUrl: string; // Your API route path (e.g., "/api/chat")
  historyApiUrl?: string; // Your history API route to keep context when signing transactions
  wallet?: WalletOptions; // Wallet configuration
  colors?: ChatComponentColors;
  options?: {
    agentName?: string; // Custom agent name
    agentImage?: string; // Custom agent image URL
    chatId?: string; // Custom chat ID
    prompt?: string // Custom Initial prompt
    localAgent?: {
      pluginId: string;
      accountId: string;
      spec: BitteOpenAPISpec;
    };
  placeholderText?: string;
  colors?: ChatComponentColors;
  customComponents?: {
    welcomeMessageComponent?: React.JSX.Element;
    mobileInputExtraButton?: React.JSX.Element; 
    messageContainer?: React.ComponentType<MessageGroupComponentProps>;
    chatContainer?: React.ComponentType<ChatContainerComponentProps>;
    inputContainer?: React.ComponentType<InputContainerProps>;
    sendButtonComponent?: React.ComponentType<SendButtonComponentProps>;
    loadingIndicator?: React.ComponentType<LoadingIndicatorComponentProps>;
    };
  };
  customToolComponents?: CustomToolComponent[];
}

export interface ReactToolComponent {
  name: string;
  component: React.ComponentType<CustomToolComponentProps>;
}

// Update CustomToolComponent type to align with BitteTool from primitives.ts
export type CustomToolComponent =
  | PortfolioTool
  | TransferTool
  | SwapTool
  | ReactToolComponent;

```

### Custom Components

The `BitteAiChat` component provides several UI elements that can be customized via the `customComponents` under options prop. This enables full control over the appearance and behavior of key parts of the chat interface by passing in your own React components.

#### Available Custom Components

You can override the following built-in UI components:

```ts
customComponents?: {
  welcomeMessageComponent?: React.JSX.Element;
  mobileInputExtraButton?: React.JSX.Element;
  messageContainer?: React.ComponentType<MessageGroupComponentProps>;
  chatContainer?: React.ComponentType<ChatContainerComponentProps>;
  inputContainer?: React.ComponentType<InputContainerProps>;
  sendButtonComponent?: React.ComponentType<SendButtonComponentProps>;
  loadingIndicator?: React.ComponentType<LoadingIndicatorComponentProps>;
};
```

#### Component Types

Below are the expected prop types for each customizable component:

**`MessageGroupComponentProps`**

```ts
export interface MessageGroupComponentProps {
  message: SmartActionAiMessage;
  isUser: boolean;
  userName: string;
  children: React.ReactNode;
  style: {
    backgroundColor: string;
    borderColor: string;
    textColor: string;
  };
  uniqueKey: string;
}
```

**`ChatContainerComponentProps`**

```ts
export interface ChatContainerComponentProps {
  children: React.ReactNode;
  style?: {
    backgroundColor?: string;
    borderColor?: string;
  };
}
```

**`InputContainerProps`**

```ts
export interface InputContainerProps {
  children: React.ReactNode;
  style?: {
    backgroundColor?: string;
    borderColor?: string;
  };
}
```

**`SendButtonComponentProps`**

```ts
export interface SendButtonComponentProps {
  input: string;
  isLoading: boolean;
  buttonColor?: string;
  textColor?: string;
  onClick?: () => void;
}
```

**`LoadingIndicatorComponentProps`**

```ts
export interface LoadingIndicatorComponentProps {
  textColor?: string;
}
```

***

### Example Usage

Here's how you might configure the `BitteAiChat` component, including some optional props and custom components:

```tsx
<BitteAiChat
  agentId={searchParams.get('agentId') || bitteAgent?.id || 'bitte-assistant'}
  apiUrl="/api/chat"
  historyApiUrl="/api/history"
  options={{
    agentName: bitteAgent?.name,
    agentImage: bitteAgent?.image,
    colors: chatColors,
  }}
  wallet={{
    near: { wallet },
    evm: evmAdapter,
  }}
  customComponents={{
    welcomeMessageComponent: <CustomWelcome />,
    mobileInputExtraButton: <ExtraMobileButton />,
    messageContainer: CustomMessageGroup,
    chatContainer: CustomChatContainer,
    inputContainer: CustomInputContainer,
    sendButtonComponent: CustomSendButton,
    loadingIndicator: CustomLoadingIndicator,
  }}
/>
```

> ✅ **Tip:** All custom components are optional. If not provided, the default components will be used.

### Custom Tool Components

The `BitteAiChat` component supports embedding interactive, data-driven tools directly into the chat UI through the `customToolComponents` prop. These tools allow users to interact with structured data returned by your agent’s API, such as portfolios, token swaps, or custom workflows.

#### How Tool Rendering Works

When your AI agent responds with a message that includes a `data` field , and that `data` matches a known type (like `PortfolioResponse`, `TransferFTData`, etc.), the chat interface can automatically render the corresponding tool component — **as long as a tool is registered in `customToolComponents` with a matching `name`**.

The `name` must match:

* The operation name in your agent’s OpenAPI spec
* The `name` field in the tool component configuration

There is **no need to wrap the response in a `tool` object** — the chat package dynamically maps the `data` to the corresponding component based on the configured name and type.\
\
**Pre-Configured Tools**

The `@bitte-ai/chat` package comes with built-in support for the following tools:

* `get-portfolio`
* `swap`
* `transfer-ft`

As long as:

* The **agent's API response includes a `data` field** that matches the correct type (`PortfolioResponse`, `SwapFTData`, or `TransferFTData` — all from `@bitte-ai/types`)
* The **OpenAPI spec defines the route using one of the above names** (i.e. `get-portfolio`, `swap`, or `transfer-ft` as operation IDs or paths)

...then the matching tool UI will automatically be rendered — **no manual configuration required**.

These tools are auto-wired and will display a default UI out-of-the-box. You only need to use `customToolComponents` if you want to **override** these default UIs or add **custom tools**.<br>

#### Example: Registering Tool Components

```tsx
const customToolComponents = [
  {
    name: 'get-portfolio', // This should match the operationId or route name from your OpenAPI spec
    component: (props) => (
      <TokenList data={props.data as PortfolioResponse} />
    ),
  },
  {
    name: 'swap',
    component: (props) => (
      <Swap data={props.data as SwapFTData} />
    ),
  },
  {
    name: 'transfer-ft',
    component: (props) => (
      <Transfer data={props.data as TransferFTData} />
    ),
  },
  {
    name: 'my-custom-tool',
    component: (props) => <MyCustomComponent data={props.data} />,
  },
];
```

Then pass them into `BitteAiChat`:

```tsx
<BitteAiChat
  agentId={searchParams.get('agentId') || bitteAgent?.id || 'bitte-assistant'}
  apiUrl="/api/chat"
  historyApiUrl="/api/history"
  wallet={{
    near: { wallet },
    evm: evmAdapter,
  }}
  options={{
    agentName: bitteAgent?.name,
    agentImage: bitteAgent?.image,
    colors: chatColors,
  }}
  customToolComponents={customToolComponents}
/>
```

#### Type Safety & Tool Interfaces

All tool types and data interfaces are available from `@bitte-ai/types`

### Available Agents

| **Agent Name**           | **Description**                                                                                  | **Category**      | **Agent ID**                                                  |
| ------------------------ | ------------------------------------------------------------------------------------------------ | ----------------- | ------------------------------------------------------------- |
| CoWSwap Assistant        | An assistant that generates transaction data for CoW Protocol Interactions                       | DeFi              | `near-cow-agent.vercel.app`                                   |
| Meme.cooking             | Generates a transaction payload for creating a new memecoin on the meme.cooking platform         | Memes             | `meme-cooking-bitte-agent.vercel.app`                         |
| CoinGecko Agent          | Provides real-time cryptocurrency data, including prices, market information, and charts         | Investing         | `coingecko-ai.vercel.app`                                     |
| Ref Finance Agent        | Provides token metadata and swaps tokens through Ref Finance                                     | DeFi              | `ref-finance-agent.vercel.app`                                |
| NEAR Roast Agent         | Roasts a NEAR account based on their on-chain activity                                           | Other             | `near-roast-agent.vercel.app`                                 |
| NEAR Staking             | Stake, unstake, and see staking information                                                      | Other             | `staking-agent.intear.tech`                                   |
| Crypto Technical Analyst | Provides in-depth market analysis and sophisticated trading insights based on technical analysis | Investing         | `technical-analysis-agent.vercel.app`                         |
| DAO Agent                | Interacts with Sputnik DAO Contracts, query DAOs, create proposals, and vote on proposals        | DAO               | `dao-agent.vercel.app`                                        |
| Safe Account Assistant   | Manages Near{Safe} Account Structure                                                             | Wallet Management | `near-safe-agent.vercel.app`                                  |
| Uniswap Assistant        | Generates transaction data for Uniswap V3 Interactions                                           | Other             | `near-uniswap-agent.vercel.app`                               |
| GrowthMate Discovery     | Discovers the ecosystem through relevant activities, news, and offers                            | Ecosystem         | `bitte-agent.vercel.app`                                      |
| Python Code Runner       | A helpful assistant for running Python code snippets                                             | Computational     | `bitte-wasmer-agent.fly.dev`                                  |
| PotLock Assistant        | Helps users search projects and donations on PotLock platform and create projects                | Other             | `potlockaiagent-hqd5dzcjajhpc3fa.eastus-01.azurewebsites.net` |
| Delta Trade DCA Helper   | Helps set up DCA plans to buy NEAR and other tokens                                              | Investing         | `dcaagent.deltatrade.ai`                                      |
| SUI Defi Agent           | check balances, swap, stake and more on SUI                                                      | Defi              | `bitte-sui-agent.vercel.app`                                  |
| Sui Explorer             | Retrieve any blockchain data from SUI                                                            | Ecosystem         | `sui-explorer.vercel.app`                                     |
| Benqi Agent              | Defi Agent for Benqi Protocol on Avalanche                                                       | Defi              | `benqi-agent.vercel.app`                                      |
| Bitte WETH Wraptor       | Wrap and unwrap ETH.                                                                             | Other             | `wraptor-agent.vercel.app`                                    |
| Bitte Distribute Tokens  | Distribute tokens on EVM chains using csv files and more                                         | Other             | `safe-airdrop-agent.vercel.app`                               |

### Creating Custom Agents

[Check out our docs for creating your own agent from an API](/agents/deploy-your-open-agent.md)

### Example Projects

* [Bitte AI Chat Boilerplate](https://github.com/BitteProtocol/chat-boilerplate)

### Styling

The component can be customized using the `colors` prop:

```typescript
type ChatComponentColors = {
  generalBackground?: string; // Chat container background
  messageBackground?: string; // Message bubble background
  textColor?: string; // Text color
  buttonColor?: string; // Button color
  borderColor?: string; // Border color
};
```

### Browser Redirects (Optional)

if you're having issues with your app or wallet containing redirects you can optionally use a history api to maintain context

Create an API route in your Next.js application that will allow your app if you want to save chat context when signing a transaction after getting redirected to the wallet.

```typescript
import { type NextRequest, NextResponse } from 'next/server';

const { BITTE_API_KEY, BITTE_API_URL = 'https://wallet.bitte.ai/api/v1' } =
  process.env;

export const dynamic = 'force-dynamic';
export const maxDuration = 60;

export const GET = async (req: NextRequest): Promise<NextResponse> => {
  const { searchParams } = new URL(req.url);
  const id = searchParams.get('id');
  const url = `${BITTE_API_URL}/history?id=${id}`;

  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${BITTE_API_KEY}`,
    },
  });

  const result = await response.json();

  return NextResponse.json(result);
};
```

###
