@smarthivelabs-devs/react-ai-nexus
v1.0.0
Published
React hooks for the SmartHive AI Nexus SDK
Readme
@smarthivelabs-devs/react-ai-nexus
React hooks and context provider for the SmartHive AI Nexus. Wraps @smarthivelabs-devs/ai-nexus in idiomatic React patterns — loading states, streaming, error handling, and cleanup are all managed for you.
What this package is: A React-specific layer. Use it in any React 18+ app (Vite, Create React App, Remix, etc.). For Next.js, also see @smarthivelabs-devs/next-ai-nexus. For React Native/Expo, see @smarthivelabs-devs/ai-nexus-expo.
Requirements
- React ≥ 18
@smarthivelabs-devs/ai-nexus≥ 1.0.0 (peer dependency)
Installation
npm install @smarthivelabs-devs/react-ai-nexus @smarthivelabs-devs/ai-nexus
# or
yarn add @smarthivelabs-devs/react-ai-nexus @smarthivelabs-devs/ai-nexusEnvironment Setup
The React hooks never expose your API key to the browser directly — they operate through a client you configure server-side (or via a proxy if needed).
# Never put this in a .env file that ships to the browser (VITE_*, NEXT_PUBLIC_*, etc.)
# For browser-based apps, use next-ai-nexus proxy handler or your own backend relay.
AI_NEXUS_API_KEY=nexus_live_xxxxxxxxxxxxxxxxxxxx
# Optional — only if self-hosting
AI_NEXUS_BASE_URL=https://api.smarthivelabs.devFor pure client-side apps (no server), point the client at a proxied endpoint:
// Calls your backend /api/nexus/* which injects the real key server-side
const nexus = new AiNexus({ apiKey: 'public', baseUrl: '/api/nexus' });Setup — AiNexusProvider
Create one AiNexus client and wrap your app. All hooks read the client from context.
// main.tsx (or _app.tsx in Next.js Pages Router)
import { AiNexus } from '@smarthivelabs-devs/ai-nexus';
import { AiNexusProvider } from '@smarthivelabs-devs/react-ai-nexus';
const nexus = new AiNexus({
apiKey: process.env.AI_NEXUS_API_KEY!,
// In a browser app pointing at a proxy:
// baseUrl: '/api/nexus',
});
export default function Root() {
return (
<AiNexusProvider client={nexus}>
<App />
</AiNexusProvider>
);
}All hooks below must be used inside a component tree wrapped by AiNexusProvider. If you forget, you'll get a clear error:
useAiNexus() must be called inside an <AiNexusProvider>.
Hook: useChat
Streaming chat with full message history, RAG support, and abort control.
import { useChat } from '@smarthivelabs-devs/react-ai-nexus';
function ChatWidget() {
const [input, setInput] = useState('');
const { messages, isLoading, error, append, reload, stop, setMessages } = useChat({
model: 'gpt-4o',
// Optional — inject retrieved documents into each user message automatically
knowledgeBaseId: 'kb_xyz789',
// Optional — prepended as the system message on every request
systemPrompt: 'You are a helpful customer support agent.',
// Optional — seed the conversation with existing messages
initialMessages: [{ role: 'assistant', content: 'Hi! How can I help?' }],
temperature: 0.7, // optional — 0 to 2
maxTokens: 1024, // optional
onFinish: (message) => {
console.log('Assistant finished:', message.content);
},
onError: (err) => {
console.error('Chat error:', err.message);
},
});
return (
<div>
{messages.map((m, i) => (
<div key={i} className={m.role === 'user' ? 'user' : 'assistant'}>
{typeof m.content === 'string' ? m.content : '[multimodal]'}
</div>
))}
{isLoading && <span>Thinking...</span>}
{error && <span className="error">{error.message}</span>}
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={() => { append(input); setInput(''); }} disabled={isLoading}>
Send
</button>
<button onClick={stop} disabled={!isLoading}>Stop</button>
<button onClick={reload} disabled={isLoading || messages.length === 0}>Retry</button>
</div>
);
}Return values:
| Field | Type | Description |
|-------|------|-------------|
| messages | ChatMessage[] | Full conversation history including the streaming assistant message |
| isLoading | boolean | true while a request is in flight |
| error | Error \| null | Last error, if any |
| append(content) | (string) => Promise<void> | Add a user message and trigger a completion |
| reload() | () => Promise<void> | Re-run the last user message |
| stop() | () => void | Abort the current streaming response |
| setMessages | Dispatch | Directly update the message list |
Hook: useAgent
Run a named agent with optional session continuity across multiple turns.
import { useAgent } from '@smarthivelabs-devs/react-ai-nexus';
function AgentPanel() {
const { run, output, sessionId, isLoading, error, reset } = useAgent({
agentId: 'agent_abc123',
onFinish: (res) => console.log('Finished. Session:', res.sessionId),
onError: (err) => console.error(err),
});
const handleAsk = async () => {
// Pass custom context to the agent on each turn
await run('What are my open tickets?', { userId: 'user_456', tier: 'pro' });
};
const handleFollowUp = async () => {
// sessionId is preserved automatically — next run continues the same session
await run('Show me only the critical ones.');
};
return (
<div>
{output && <p>{output}</p>}
{sessionId && <small>Session: {sessionId}</small>}
{isLoading && <span>Running agent...</span>}
{error && <span className="error">{error.message}</span>}
<button onClick={handleAsk} disabled={isLoading}>Ask</button>
<button onClick={handleFollowUp} disabled={isLoading || !sessionId}>Follow up</button>
<button onClick={reset}>New session</button>
</div>
);
}Return values:
| Field | Type | Description |
|-------|------|-------------|
| run(input, context?) | (string, object?) => Promise<AgentsRunResponse> | Run the agent. Automatically uses sessionId from previous turn. |
| output | string \| null | Last response text |
| sessionId | string \| null | Current session ID (auto-maintained across turns) |
| isLoading | boolean | true while agent is running |
| error | Error \| null | Last error |
| reset() | () => void | Clear output and start a fresh session next call |
Hook: useVoiceCall
Full realtime voice session lifecycle. Creates a LiveKit session, polls for transcripts, and handles recording.
import { useVoiceCall } from '@smarthivelabs-devs/react-ai-nexus';
import { Room } from 'livekit-client';
function VoiceCallPanel() {
const roomRef = useRef<Room | null>(null);
const {
state, // 'idle' | 'connecting' | 'active' | 'ending' | 'ended' | 'error'
sessionId,
livekitUrl, // pass to livekit-client Room.connect()
livekitToken, // pass to livekit-client Room.connect()
isMuted,
toggleMute,
transcript, // TranscriptEntry[] — { role, text, timestamp }[]
error,
start,
end,
startRecording,
stopRecording,
} = useVoiceCall({
agentId: 'agent_abc123',
mode: 'voice', // 'voice' | 'text' | 'duplex'
features: ['transcription', 'tts', 'vad'],
// How often to poll the server for new transcript entries (ms)
// Default: 2000
pollIntervalMs: 2_000,
onTranscript: (entry) => {
console.log(`${entry.role} [${entry.timestamp}]: ${entry.text}`);
},
onError: (err) => console.error('Voice call error:', err),
});
// Connect to LiveKit once the session is active
useEffect(() => {
if (state === 'active' && livekitUrl && livekitToken) {
const room = new Room();
roomRef.current = room;
room.connect(livekitUrl, livekitToken).catch(console.error);
return () => { void room.disconnect(); };
}
}, [state, livekitUrl, livekitToken]);
const handleStopRecording = async () => {
const url = await stopRecording('mp4');
console.log('Recording available at:', url);
};
return (
<div>
<p>Status: <strong>{state}</strong></p>
{state === 'idle' && (
<button onClick={start}>Start call</button>
)}
{state === 'active' && (
<>
<button onClick={end}>End call</button>
<button onClick={toggleMute}>{isMuted ? 'Unmute' : 'Mute'}</button>
<button onClick={startRecording}>Record</button>
<button onClick={handleStopRecording}>Stop recording</button>
</>
)}
{error && <p className="error">{error.message}</p>}
<div className="transcript">
{transcript.map((t, i) => (
<p key={i}><strong>{t.role}</strong>: {t.text}</p>
))}
</div>
</div>
);
}Return values:
| Field | Type | Description |
|-------|------|-------------|
| state | VoiceCallState | Current lifecycle state |
| sessionId | string \| null | Session ID once active |
| livekitUrl | string \| null | WebSocket URL for LiveKit room connection |
| livekitToken | string \| null | JWT token for LiveKit room connection |
| isMuted | boolean | Local mute state (you must wire this to your LiveKit participant) |
| toggleMute() | () => void | Toggles isMuted |
| transcript | TranscriptEntry[] | Running list of conversation turns |
| error | Error \| null | Last error |
| start() | () => Promise<void> | Create session, set livekitUrl/livekitToken, begin polling |
| end() | () => Promise<void> | End session and stop polling |
| startRecording() | () => Promise<void> | Start server-side recording |
| stopRecording(format?) | (format?) => Promise<string \| null> | Stop recording, returns URL |
VoiceCallState transitions:
idle → connecting → active → ending → ended
↘ errorHook: useImages
Generate images with automatic job polling. The hook handles the queued → processing → completed lifecycle.
import { useImages } from '@smarthivelabs-devs/react-ai-nexus';
function ImageGenerator() {
const { generate, job, isLoading, error } = useImages();
const handleGenerate = async () => {
const result = await generate({
prompt: 'A serene Japanese garden at dawn',
size: '1024x1024',
quality: 'hd',
style: 'natural',
});
// result.status === 'completed' here
console.log(result.data?.[0]?.url);
};
return (
<div>
<button onClick={handleGenerate} disabled={isLoading}>
{isLoading ? `Generating... (${job?.status})` : 'Generate'}
</button>
{error && <p className="error">{error.message}</p>}
{job?.status === 'completed' && job.data?.[0]?.url && (
<img src={job.data[0].url} alt="Generated" />
)}
</div>
);
}Return values:
| Field | Type | Description |
|-------|------|-------------|
| generate(params) | (ImagesGenerateParams) => Promise<ImageJob> | Start generation and poll until complete |
| job | ImageJob \| null | Live job state (updates every 2 s during processing) |
| isLoading | boolean | true while generating or polling |
| error | Error \| null | Last error |
Hook: useModeration
Check content before sending it to other APIs or storing it.
import { useModeration } from '@smarthivelabs-devs/react-ai-nexus';
function CommentForm() {
const { moderate, result, isLoading } = useModeration();
const [text, setText] = useState('');
const handleSubmit = async () => {
const check = await moderate({ input: text });
if (check.flagged) {
alert('Content violates policy');
return;
}
// safe to submit
};
return (
<div>
<textarea value={text} onChange={e => setText(e.target.value)} />
{result?.flagged && <p className="warning">Content flagged</p>}
<button onClick={handleSubmit} disabled={isLoading}>Submit</button>
</div>
);
}Hook: useDocuments
Manage a knowledge base: list, ingest, and delete documents.
import { useDocuments } from '@smarthivelabs-devs/react-ai-nexus';
function KnowledgeBasePanel() {
const { documents, ingest, remove, refresh, isLoading, error } = useDocuments({
knowledgeBaseId: 'kb_xyz789', // optional — scope to one KB
autoFetch: true, // auto-load on mount (default: true)
});
const handleIngest = async () => {
await ingest({
storageUrl: 'https://storage.example.com/q1-report.pdf',
knowledgeBaseId: 'kb_xyz789',
metadata: { quarter: 'Q1 2026' },
});
// list refreshes automatically after ingest
};
return (
<div>
<button onClick={handleIngest}>Add Document</button>
<button onClick={refresh}>Refresh</button>
{isLoading && <span>Loading...</span>}
{error && <span className="error">{error.message}</span>}
<ul>
{documents.map(doc => (
<li key={doc.documentId}>
{doc.filename ?? doc.documentId} — {doc.status}
{doc.status === 'completed' && ` (${doc.chunkCount} chunks)`}
<button onClick={() => remove(doc.documentId)}>Delete</button>
</li>
))}
</ul>
</div>
);
}Hook: useEmbeddings
Generate embeddings on demand.
import { useEmbeddings } from '@smarthivelabs-devs/react-ai-nexus';
function EmbeddingsDemo() {
const { embed, embeddings, isLoading, error } = useEmbeddings();
const handleEmbed = async () => {
await embed({
model: 'text-embedding-3-small',
input: ['Hello world', 'Goodbye world'],
});
};
return (
<div>
<button onClick={handleEmbed} disabled={isLoading}>Embed</button>
{embeddings && (
<p>Got {embeddings.data.length} embeddings of size {embeddings.data[0]?.embedding.length}</p>
)}
</div>
);
}Direct Client Access
For anything not covered by the hooks, call the underlying client directly:
import { useAiNexus } from '@smarthivelabs-devs/react-ai-nexus';
function HealthBadge() {
const nexus = useAiNexus();
const [status, setStatus] = useState<string>('checking...');
useEffect(() => {
nexus.health.check().then(h => setStatus(h.status));
}, [nexus]);
return <span className={`badge badge-${status}`}>{status}</span>;
}TypeScript
All hook option and return types are exported:
import type {
UseChatOptions, UseChatReturn,
UseAgentOptions, UseAgentReturn,
UseVoiceCallOptions, UseVoiceCallReturn, VoiceCallState, TranscriptEntry,
UseImagesReturn,
UseModerationReturn,
UseDocumentsOptions, UseDocumentsReturn,
} from '@smarthivelabs-devs/react-ai-nexus';
// Shared types from the core SDK
import type { ChatMessage, ImageJob, ModelProfile } from '@smarthivelabs-devs/ai-nexus';License
MIT — © SmartHive Labs
