# 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](https://docs.bitte.ai/agents/deploy-your-open-agent)

### 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);
};
```

###


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.bitte.ai/agents/embeddable-chat-component.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
