Skip to main content

Build a Chatbot in 5 Minutes

This tutorial walks through building a chatbot with credit tracking and persistent memory using the AINative React SDK.

Prerequisites

1. Create a React App

npx create-react-app my-chatbot --template typescript
cd my-chatbot
npm install @ainative/react-sdk

2. Set Your API Key

Create a .env file in the project root:

REACT_APP_AINATIVE_KEY=your-api-key-here
warning

Never commit .env files to version control. Add .env to your .gitignore.

3. Set Up the Provider

Replace src/App.tsx with:

import React from 'react';
import { AINativeProvider } from '@ainative/react-sdk';
import { Chatbot } from './Chatbot';

export default function App() {
return (
<AINativeProvider config={{ apiKey: process.env.REACT_APP_AINATIVE_KEY! }}>
<Chatbot />
</AINativeProvider>
);
}

The AINativeProvider makes the API key available to all hooks in the component tree. It defaults to https://api.ainative.studio/api/v1 as the base URL.

4. Build the Chat Component

Create src/Chatbot.tsx:

import React, { useState, useRef, useEffect } from 'react';
import { useChat, useCredits } from '@ainative/react-sdk';
import type { Message } from '@ainative/react-sdk';

export function Chatbot() {
const { messages, sendMessage, isLoading, error, reset } = useChat({
model: 'meta-llama/llama-3.3-70b-instruct',
temperature: 0.7,
onError: (err) => console.error('Chat error:', err),
});

const { balance } = useCredits();
const [input, setInput] = useState('');
const bottomRef = useRef<HTMLDivElement>(null);

// Auto-scroll to bottom when new messages arrive
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);

const handleSend = async () => {
const text = input.trim();
if (!text || isLoading) return;

setInput('');

// Build the full message array with the new user message
const updatedMessages: Message[] = [
...messages,
{ role: 'user', content: text },
];

await sendMessage(updatedMessages);
};

return (
<div style={styles.container}>
{/* Header with credit balance */}
<header style={styles.header}>
<h2 style={{ margin: 0 }}>AI Chatbot</h2>
{balance && (
<span style={styles.credits}>
{balance.remaining_credits.toLocaleString()} credits
</span>
)}
</header>

{/* Message list */}
<div style={styles.messageArea}>
{messages.length === 0 && (
<p style={styles.placeholder}>
Send a message to start chatting.
</p>
)}

{messages.map((msg, i) => (
<div
key={i}
style={{
...styles.bubble,
alignSelf: msg.role === 'user' ? 'flex-end' : 'flex-start',
background: msg.role === 'user' ? '#0066ff' : '#f0f0f0',
color: msg.role === 'user' ? '#fff' : '#000',
}}
>
{msg.content}
</div>
))}

{isLoading && (
<div style={{ ...styles.bubble, background: '#f0f0f0', color: '#999' }}>
Thinking...
</div>
)}

{error && (
<div style={{ ...styles.bubble, background: '#fee', color: '#c00' }}>
Error: {error.message}
</div>
)}

<div ref={bottomRef} />
</div>

{/* Input area */}
<div style={styles.inputArea}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
placeholder="Type a message..."
style={styles.input}
disabled={isLoading}
/>
<button onClick={handleSend} disabled={isLoading} style={styles.sendBtn}>
Send
</button>
<button onClick={reset} style={styles.clearBtn}>
Clear
</button>
</div>
</div>
);
}

const styles: Record<string, React.CSSProperties> = {
container: {
maxWidth: 640,
margin: '40px auto',
fontFamily: '-apple-system, BlinkMacSystemFont, sans-serif',
display: 'flex',
flexDirection: 'column',
height: '80vh',
},
header: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '12px 16px',
borderBottom: '1px solid #eee',
},
credits: {
fontSize: 14,
color: '#666',
background: '#f5f5f5',
padding: '4px 10px',
borderRadius: 12,
},
messageArea: {
flex: 1,
overflowY: 'auto',
padding: 16,
display: 'flex',
flexDirection: 'column',
gap: 8,
},
placeholder: {
textAlign: 'center',
color: '#999',
marginTop: 100,
},
bubble: {
maxWidth: '80%',
padding: '10px 14px',
borderRadius: 16,
lineHeight: 1.5,
whiteSpace: 'pre-wrap',
},
inputArea: {
display: 'flex',
gap: 8,
padding: 16,
borderTop: '1px solid #eee',
},
input: {
flex: 1,
padding: '10px 14px',
borderRadius: 8,
border: '1px solid #ddd',
fontSize: 15,
outline: 'none',
},
sendBtn: {
padding: '10px 20px',
borderRadius: 8,
border: 'none',
background: '#0066ff',
color: '#fff',
cursor: 'pointer',
fontSize: 15,
},
clearBtn: {
padding: '10px 14px',
borderRadius: 8,
border: '1px solid #ddd',
background: '#fff',
cursor: 'pointer',
fontSize: 15,
},
};

5. Run It

npm start

Open http://localhost:3000. You have a working chatbot with:

  • Chat completions via Llama 3.3 70B
  • Credit balance display
  • Auto-scrolling message list
  • Loading and error states

6. Add Memory (Optional)

Want the chatbot to remember things across sessions? Add the useMemory hook:

import { useChat, useCredits, useMemory } from '@ainative/react-sdk';

export function ChatbotWithMemory() {
const { messages, sendMessage, isLoading, reset } = useChat({
model: 'meta-llama/llama-3.3-70b-instruct',
});
const { balance } = useCredits();
const { remember, recall } = useMemory('chatbot-user');
const [input, setInput] = useState('');

const handleSend = async () => {
const text = input.trim();
if (!text || isLoading) return;
setInput('');

// Check if the user is asking about something we remember
const relatedMemories = await recall(text, { limit: 3 });

// Build context from memories
const memoryContext = relatedMemories.length > 0
? `\n\nRelevant context from previous conversations:\n${relatedMemories.map(m => `- ${m.content}`).join('\n')}`
: '';

const systemMessage = {
role: 'system' as const,
content: `You are a helpful assistant.${memoryContext}`,
};

const response = await sendMessage([
systemMessage,
...messages,
{ role: 'user', content: text },
]);

// Remember important parts of the conversation
if (response) {
await remember(`User asked: ${text}`, {
tags: ['conversation'],
importance: 0.6,
});
}
};

// ... render same UI as before
}

How It Works

  1. AINativeProvider sets up a React context with your API key.
  2. useChat manages the message array. When you call sendMessage(messages), it POSTs to /public/managed-chat/chat/completions and appends the assistant reply.
  3. useCredits fetches your balance from /public/credits/balance on mount.
  4. useMemory stores and recalls facts via the ZeroMemory API (/public/memory/v2/remember and /public/memory/v2/recall).

Error Handling

The useChat hook provides an error field and an onError callback. Common status codes:

StatusMeaningWhat to Do
401Auth expiredRedirect to login or refresh token
429Rate limitedWait and retry (see Retry-After header)
503Service unavailableTry a different model
const { error } = useChat({
onError: (err) => {
if (err.status === 429) {
showToast('Rate limited. Waiting...');
}
},
});

Next Steps