@caava-ai/react
v0.1.1
Published
React hooks and components for Akili AI SDK
Downloads
9
Readme
@caava-ai/react
React hooks and components for building AI-powered chat applications with file upload support.
Features
- 🎯 useAgentChat - Complete chat functionality with streaming
- ⚡ useOptimizedStream - Optimized streaming with branching and reconnection
- 📎 useFileUpload - File upload with drag & drop, paste, and validation
- 🔄 OptimizedStreamProvider - Context provider for stream management
- 📱 AgentChat - Ready-to-use chat component
Installation
npm install @caava-ai/react @caava-ai/coreHooks
useAgentChat
Complete chat functionality with streaming support:
import { useAgentChat } from "@caava-ai/react";
function ChatComponent() {
const { messages, sendMessage, isStreaming } = useAgentChat({
assistantId: "your-assistant-id",
apiUrl: "https://api.caava.ai",
apiKey: "your-api-key",
onMessage: (message) => console.log("New message:", message),
onError: (error) => console.error("Chat error:", error),
});
return (
<div>
{messages.map((msg, i) => (
<div key={i}>{msg.content}</div>
))}
<button onClick={() => sendMessage("Hello!")} disabled={isStreaming}>
Send
</button>
</div>
);
}useOptimizedStream
Advanced streaming with branching and state management:
import { useOptimizedStream } from "@caava-ai/react";
function StreamingComponent() {
const { messages, sendMessage, isStreaming, branchFromMessage } =
useOptimizedStream({
assistantId: "your-assistant-id",
enableBranching: true,
maxRetries: 3,
});
return (
<div>
{messages.map((msg, i) => (
<div key={i}>
{msg.content}
<button onClick={() => branchFromMessage(i)}>Branch from here</button>
</div>
))}
</div>
);
}useFileUpload
Comprehensive file upload functionality with drag & drop, paste support, and validation:
import { useFileUpload } from "@caava-ai/react";
function FileUploadComponent() {
const {
files,
isDragOver,
isUploading,
fileInputRef,
handleFileSelect,
handleDragOver,
handleDragLeave,
handleDrop,
handlePaste,
removeFile,
clearFiles,
openFilePicker,
} = useFileUpload({
maxFileSize: 10, // 10MB
supportedFileTypes: ["image/jpeg", "image/png", "application/pdf"],
maxFiles: 5,
onFileUpload: (files) => {
console.log("Files uploaded:", files);
},
onError: (error) => {
console.error("Upload error:", error);
},
});
return (
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onPaste={handlePaste}
style={{
border: isDragOver ? "2px dashed #3b82f6" : "2px dashed #e5e7eb",
borderRadius: "8px",
padding: "20px",
textAlign: "center",
}}
>
{/* Hidden file input */}
<input
ref={fileInputRef}
type="file"
multiple
accept={supportedFileTypes.join(",")}
style={{ display: "none" }}
onChange={(e) => handleFileSelect(e.target.files)}
/>
{/* Upload button */}
<button onClick={openFilePicker} disabled={isUploading}>
📎 Choose Files
</button>
{/* File previews */}
{files.length > 0 && (
<div style={{ marginTop: "16px" }}>
{files.map((fileItem, index) => (
<div
key={index}
style={{
display: "flex",
alignItems: "center",
gap: "8px",
marginBottom: "8px",
}}
>
{/* Image preview */}
{fileItem.preview && (
<img
src={fileItem.preview}
alt={fileItem.file.name}
style={{ width: "40px", height: "40px", objectFit: "cover" }}
/>
)}
{/* File info */}
<div>
<div>{fileItem.file.name}</div>
<div style={{ fontSize: "12px", color: "#6b7280" }}>
{(fileItem.file.size / 1024 / 1024).toFixed(2)} MB
</div>
{fileItem.status === "error" && (
<div style={{ color: "#ef4444", fontSize: "12px" }}>
{fileItem.error}
</div>
)}
</div>
{/* Remove button */}
<button
onClick={() => removeFile(index)}
style={{ marginLeft: "auto" }}
>
×
</button>
</div>
))}
<button onClick={clearFiles}>Clear All</button>
</div>
)}
<p style={{ margin: "16px 0 0 0", fontSize: "14px", color: "#6b7280" }}>
Drop files here, click to upload, or paste from clipboard
</p>
</div>
);
}useFileUpload Options
interface UseFileUploadOptions {
maxFileSize?: number; // Maximum file size in MB (default: 10)
supportedFileTypes?: string[]; // Array of MIME types (default: images + PDF)
maxFiles?: number; // Maximum number of files (default: 10)
onFileUpload?: (files: File[]) => void; // Callback when files are ready
onError?: (error: string) => void; // Error callback
}useFileUpload Return
interface UseFileUploadReturn {
files: UploadingFile[]; // Current files with status and previews
isDragOver: boolean; // True when dragging files over component
isUploading: boolean; // True when processing files
fileInputRef: React.RefObject<HTMLInputElement>; // Ref for hidden file input
// Actions
handleFileSelect: (files: FileList | null) => void; // Handle file input change
handleDragOver: (e: React.DragEvent) => void; // Handle drag over
handleDragLeave: (e: React.DragEvent) => void; // Handle drag leave
handleDrop: (e: React.DragEvent) => void; // Handle file drop
handlePaste: (e: React.ClipboardEvent) => void; // Handle clipboard paste
removeFile: (index: number) => void; // Remove file by index
clearFiles: () => void; // Clear all files
openFilePicker: () => void; // Programmatically open file picker
}File Upload Features
- Drag & Drop: Drag files directly onto any element with drag handlers
- File Picker: Click button to open native file selection dialog
- Clipboard Paste: Paste images directly from clipboard (Ctrl+V)
- File Validation: Automatic validation of file size and type
- Image Previews: Automatic base64 preview generation for images
- Error Handling: Comprehensive error messages for validation failures
- Multiple Files: Support for multiple file selection and management
- Type Safety: Full TypeScript support with detailed type definitions
Components
AgentChat
Ready-to-use chat component:
import { AgentChat } from "@caava-ai/react";
function App() {
return (
<AgentChat
assistantId="your-assistant-id"
apiKey="your-api-key"
title="Customer Support"
enableFileUpload={true}
maxFileSize={10}
onMessage={(message) => console.log(message)}
onFileUpload={(files) => console.log("Files:", files)}
/>
);
}Providers
OptimizedStreamProvider
Context provider for managing streaming state:
import { OptimizedStreamProvider } from "@caava-ai/react";
function App() {
return (
<OptimizedStreamProvider>
<YourChatComponents />
</OptimizedStreamProvider>
);
}Complete File Upload Example
Here's a complete example showing how to integrate file upload with chat:
import React, { useState } from "react";
import { useAgentChat, useFileUpload } from "@caava-ai/react";
function ChatWithFileUpload() {
const [inputValue, setInputValue] = useState("");
const { messages, sendMessage, isStreaming } = useAgentChat({
assistantId: "your-assistant-id",
apiKey: "your-api-key",
});
const {
files,
isDragOver,
handleDragOver,
handleDragLeave,
handleDrop,
handlePaste,
removeFile,
clearFiles,
fileInputRef,
openFilePicker,
handleFileSelect,
} = useFileUpload({
maxFileSize: 10,
supportedFileTypes: ["image/jpeg", "image/png", "application/pdf"],
onFileUpload: (uploadedFiles) => {
console.log("Files ready for sending:", uploadedFiles);
},
});
const handleSendMessage = async () => {
if (!inputValue.trim() && files.length === 0) return;
// Prepare message content with text and files
const content = [];
if (inputValue.trim()) {
content.push({
type: "text",
text: inputValue.trim(),
});
}
files.forEach((fileItem) => {
if (fileItem.status === "completed") {
if (fileItem.file.type.startsWith("image/") && fileItem.preview) {
content.push({
type: "image_url",
image_url: { url: fileItem.preview },
});
} else if (fileItem.file.type === "application/pdf") {
content.push({
type: "media",
media: {
type: "document",
name: fileItem.file.name,
size: fileItem.file.size,
mime_type: fileItem.file.type,
},
});
}
}
});
if (content.length > 0) {
await sendMessage(content);
setInputValue("");
clearFiles();
}
};
return (
<div
style={{
width: "400px",
height: "500px",
border: "1px solid #e5e7eb",
borderRadius: "8px",
display: "flex",
flexDirection: "column",
}}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
{/* Messages */}
<div style={{ flex: 1, padding: "16px", overflowY: "auto" }}>
{messages.map((msg, i) => (
<div key={i} style={{ marginBottom: "12px" }}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
{isStreaming && <div>Assistant is typing...</div>}
</div>
{/* File Previews */}
{files.length > 0 && (
<div
style={{
padding: "12px",
borderTop: "1px solid #e5e7eb",
backgroundColor: "#f9fafb",
}}
>
{files.map((fileItem, index) => (
<div
key={index}
style={{
display: "flex",
alignItems: "center",
gap: "8px",
marginBottom: "8px",
}}
>
{fileItem.preview && (
<img
src={fileItem.preview}
alt={fileItem.file.name}
style={{ width: "32px", height: "32px", objectFit: "cover" }}
/>
)}
<span style={{ fontSize: "12px" }}>{fileItem.file.name}</span>
<button onClick={() => removeFile(index)}>×</button>
</div>
))}
</div>
)}
{/* Input */}
<div
style={{
padding: "16px",
borderTop: "1px solid #e5e7eb",
display: "flex",
gap: "8px",
}}
>
<input
ref={fileInputRef}
type="file"
multiple
style={{ display: "none" }}
onChange={(e) => handleFileSelect(e.target.files)}
/>
<button onClick={openFilePicker}>📎</button>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onPaste={handlePaste}
placeholder="Type a message..."
style={{ flex: 1 }}
/>
<button
onClick={handleSendMessage}
disabled={isStreaming || (!inputValue.trim() && files.length === 0)}
>
Send
</button>
</div>
{/* Drag Overlay */}
{isDragOver && (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(59, 130, 246, 0.1)",
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "2px dashed #3b82f6",
}}
>
<div style={{ textAlign: "center" }}>
<div style={{ fontSize: "48px" }}>📎</div>
<p>Drop your files here</p>
</div>
</div>
)}
</div>
);
}
export default ChatWithFileUpload;TypeScript Support
All hooks and components are fully typed:
import type {
UseAgentChatOptions,
UseAgentChatReturn,
UseOptimizedStreamOptions,
UseOptimizedStreamReturn,
UseFileUploadOptions,
UseFileUploadReturn,
UploadingFile,
} from "@caava-ai/react";Dependencies
@caava-ai/core- Core types and utilitiesreact>= 18.0.0react-dom>= 18.0.0
License
MIT
