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
- Node.js 18+
- An AINative API key (get one free)
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
AINativeProvidersets up a React context with your API key.useChatmanages the message array. When you callsendMessage(messages), it POSTs to/public/managed-chat/chat/completionsand appends the assistant reply.useCreditsfetches your balance from/public/credits/balanceon mount.useMemorystores and recalls facts via the ZeroMemory API (/public/memory/v2/rememberand/public/memory/v2/recall).
Error Handling
The useChat hook provides an error field and an onError callback. Common status codes:
| Status | Meaning | What to Do |
|---|---|---|
401 | Auth expired | Redirect to login or refresh token |
429 | Rate limited | Wait and retry (see Retry-After header) |
503 | Service unavailable | Try a different model |
const { error } = useChat({
onError: (err) => {
if (err.status === 429) {
showToast('Rate limited. Waiting...');
}
},
});
Next Steps
- React SDK Reference -- Full hook API docs
- Next.js SDK -- Add server-side auth and API routes
- ZeroMemory -- Deep dive into persistent memory
- RAG Pipeline -- Build a document Q&A system